diff --git a/cli/src/cli.rs b/cli/src/cli.rs index fc7518a38e..8e6842e446 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -42,7 +42,9 @@ use solana_faucet::faucet_mock::request_airdrop_transaction; use solana_rbpf::vm::{Config, Executable}; use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ + account::Account, bpf_loader, bpf_loader_deprecated, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::{Epoch, Slot}, commitment_config::CommitmentConfig, decode_error::DecodeError, @@ -185,12 +187,26 @@ pub enum CliCommand { lamports: u64, }, // Program Deployment - Deploy { + ProgramDeploy { program_location: String, - address: Option, + buffer: Option, use_deprecated_loader: bool, + use_upgradeable_loader: bool, + upgrade_authority: Option, + max_len: Option, allow_excessive_balance: bool, }, + ProgramUpgrade { + program_location: String, + program: Pubkey, + upgrade_authority: SignerIndex, + buffer: Option, + }, + SetProgramUpgradeAuthority { + program: Pubkey, + upgrade_authority: SignerIndex, + new_upgrade_authority: Option, + }, // Stake Commands CreateStakeAccount { stake_account: SignerIndex, @@ -616,24 +632,78 @@ pub fn parse_command( parse_withdraw_from_nonce_account(matches, default_signer, wallet_manager) } // Program Deployment - ("deploy", Some(matches)) => { - let (address_signer, _address) = signer_of(matches, "address_signer", wallet_manager)?; + ("program-deploy", Some(matches)) => { let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?]; - let address = address_signer.map(|signer| { + let (buffer_signer, _address) = signer_of(matches, "buffer_signer", wallet_manager)?; + let buffer = buffer_signer.map(|signer| { signers.push(signer); 1 }); + let upgrade_authority = pubkey_of(matches, "upgrade_authority"); + let max_len = value_of(matches, "max_len"); + Ok(CliCommandInfo { - command: CliCommand::Deploy { + command: CliCommand::ProgramDeploy { program_location: matches.value_of("program_location").unwrap().to_string(), - address, + buffer, use_deprecated_loader: matches.is_present("use_deprecated_loader"), + use_upgradeable_loader: matches.is_present("use_upgradeable_loader"), + upgrade_authority, + max_len, allow_excessive_balance: matches.is_present("allow_excessive_balance"), }, signers, }) } + ("program-upgrade", Some(matches)) => { + let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?]; + let (upgrade_authority_signer, _address) = + signer_of(matches, "upgrade_authority", wallet_manager)?; + let upgrade_authority = upgrade_authority_signer + .map(|signer| { + signers.push(signer); + 1 + }) + .unwrap(); + let (buffer_signer, _address) = signer_of(matches, "buffer_signer", wallet_manager)?; + let buffer = buffer_signer.map(|signer| { + signers.push(signer); + 2 + }); + let program = pubkey_of(matches, "program_id").unwrap(); + + Ok(CliCommandInfo { + command: CliCommand::ProgramUpgrade { + program_location: matches.value_of("program_location").unwrap().to_string(), + program, + upgrade_authority, + buffer, + }, + signers, + }) + } + ("program-set-upgrade-authority", Some(matches)) => { + let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?]; + let (upgrade_authority_signer, _address) = + signer_of(matches, "upgrade_authority", wallet_manager)?; + let upgrade_authority = upgrade_authority_signer + .map(|signer| { + signers.push(signer); + 1 + }) + .unwrap(); + let program = pubkey_of(matches, "program_id").unwrap(); + let new_upgrade_authority = pubkey_of(matches, "new_upgrade_authority"); + Ok(CliCommandInfo { + command: CliCommand::SetProgramUpgradeAuthority { + program, + upgrade_authority, + new_upgrade_authority, + }, + signers, + }) + } ("wait-for-max-stake", Some(matches)) => { let max_stake_percent = value_t_or_exit!(matches, "max_percent", f32); Ok(CliCommandInfo { @@ -1170,63 +1240,7 @@ fn send_and_confirm_transactions_with_spinner( } } -fn process_deploy( - rpc_client: &RpcClient, - config: &CliConfig, - program_location: &str, - address: Option, - use_deprecated_loader: bool, - allow_excessive_balance: bool, -) -> ProcessResult { - const WORDS: usize = 12; - // Create ephemeral keypair to use for program address, if not provided - let mnemonic = Mnemonic::new(MnemonicType::for_word_count(WORDS)?, Language::English); - let seed = Seed::new(&mnemonic, ""); - let new_keypair = keypair_from_seed(seed.as_bytes())?; - - let result = do_process_deploy( - rpc_client, - config, - program_location, - address, - use_deprecated_loader, - allow_excessive_balance, - new_keypair, - ); - - if result.is_err() && address.is_none() { - let phrase: &str = mnemonic.phrase(); - let divider = String::from_utf8(vec![b'='; phrase.len()]).unwrap(); - eprintln!( - "{}\nTo reuse this address, recover the ephemeral keypair file with", - divider - ); - eprintln!( - "`solana-keygen recover` and the following {}-word seed phrase,", - WORDS - ); - eprintln!( - "then pass it as the [PROGRAM_ADDRESS_SIGNER] argument to `solana deploy ...`\n{}\n{}\n{}", - divider, phrase, divider - ); - } - result -} - -fn do_process_deploy( - rpc_client: &RpcClient, - config: &CliConfig, - program_location: &str, - address: Option, - use_deprecated_loader: bool, - allow_excessive_balance: bool, - new_keypair: Keypair, -) -> ProcessResult { - let program_id = if let Some(i) = address { - config.signers[i] - } else { - &new_keypair - }; +fn read_and_verify_elf(program_location: &str) -> Result, Box> { let mut file = File::open(program_location).map_err(|err| { CliError::DynamicProgramError(format!("Unable to open program file: {}", err)) })?; @@ -1235,6 +1249,7 @@ fn do_process_deploy( CliError::DynamicProgramError(format!("Unable to read program file: {}", err)) })?; + // Verify the program Executable::::from_elf( &program_data, Some(|x| bpf_verifier::check(x, false)), @@ -1242,70 +1257,287 @@ fn do_process_deploy( ) .map_err(|err| CliError::DynamicProgramError(format!("ELF error: {}", err)))?; - let loader_id = if use_deprecated_loader { - bpf_loader_deprecated::id() + Ok(program_data) +} + +fn complete_partial_program_init( + loader_id: &Pubkey, + payer_pubkey: &Pubkey, + elf_pubkey: &Pubkey, + account: &Account, + account_data_len: usize, + minimum_balance: u64, + allow_excessive_balance: bool, +) -> Result<(Vec, u64), Box> { + let mut instructions: Vec = vec![]; + let mut balance_needed = 0; + if account.executable { + return Err(CliError::DynamicProgramError( + "Buffer account is already executable".to_string(), + ) + .into()); + } + if account.owner != *loader_id && !system_program::check_id(&account.owner) { + return Err(CliError::DynamicProgramError( + "Buffer account is already owned by another account".to_string(), + ) + .into()); + } + + if account.data.is_empty() && system_program::check_id(&account.owner) { + instructions.push(system_instruction::allocate( + elf_pubkey, + account_data_len as u64, + )); + if account.owner != *loader_id { + instructions.push(system_instruction::assign(elf_pubkey, &loader_id)); + } + } + if account.lamports < minimum_balance { + let balance = minimum_balance - account.lamports; + instructions.push(system_instruction::transfer( + payer_pubkey, + elf_pubkey, + balance, + )); + balance_needed = balance; + } else if account.lamports > minimum_balance + && system_program::check_id(&account.owner) + && !allow_excessive_balance + { + return Err(CliError::DynamicProgramError(format!( + "Buffer account has a balance: {:?}; it may already be in use", + Sol(account.lamports) + )) + .into()); + } + Ok((instructions, balance_needed)) +} + +fn check_payer( + rpc_client: &RpcClient, + config: &CliConfig, + balance_needed: u64, + messages: &[&Message], +) -> Result<(), Box> { + let (_, fee_calculator, _) = rpc_client + .get_recent_blockhash_with_commitment(config.commitment)? + .value; + + // Does the payer have enough? + check_account_for_spend_multiple_fees_with_commitment( + rpc_client, + &config.signers[0].pubkey(), + balance_needed, + &fee_calculator, + messages, + config.commitment, + )?; + Ok(()) +} + +fn send_deploy_messages( + rpc_client: &RpcClient, + config: &CliConfig, + initial_message: &Option, + write_messages: &[Message], + final_message: &Message, + buffer_signer: &dyn Signer, + program_signer: &dyn Signer, +) -> Result<(), Box> { + if let Some(message) = initial_message { + trace!("Preparing the required accounts"); + + 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(&[config.signers[0], buffer_signer], blockhash)?; + } else { + initial_transaction.try_sign(&[config.signers[0]], 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| { + CliError::DynamicProgramError(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(&[config.signers[0], buffer_signer], blockhash)?; + write_transactions.push(tx); + } + send_and_confirm_transactions_with_spinner( + &rpc_client, + write_transactions, + &[config.signers[0], buffer_signer], + config.commitment, + last_valid_slot, + ) + .map_err(|err| { + CliError::DynamicProgramError(format!("Data writes to account failed: {}", err)) + })?; + + trace!("Deploying program account"); + let (blockhash, _, _) = rpc_client + .get_recent_blockhash_with_commitment(config.commitment)? + .value; + + let mut final_tx = Transaction::new_unsigned(final_message.clone()); + final_tx.try_sign(&[config.signers[0], program_signer], blockhash)?; + rpc_client + .send_and_confirm_transaction_with_spinner_and_config( + &final_tx, + config.commitment, + RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + }, + ) + .map_err(|e| { + CliError::DynamicProgramError(format!("Deploying program account failed: {}", e)) + })?; + Ok(()) +} + +fn process_program_deploy( + rpc_client: &RpcClient, + config: &CliConfig, + program_location: &str, + buffer: Option, + use_deprecated_loader: bool, + use_upgradeable_loader: bool, + upgrade_authority: Option, + max_len: Option, + allow_excessive_balance: bool, +) -> ProcessResult { + // Create ephemeral keypair to use for Buffer account, if not provided + let (words, mnemonic, new_keypair) = create_ephemeral_keypair()?; + let result = do_process_program_deploy( + rpc_client, + config, + program_location, + buffer, + use_deprecated_loader, + use_upgradeable_loader, + upgrade_authority, + max_len, + allow_excessive_balance, + new_keypair, + ); + if result.is_err() && buffer.is_none() { + report_ephemeral_mnemonic(words, mnemonic); + } + result +} + +#[allow(clippy::too_many_arguments)] +fn do_process_program_deploy( + rpc_client: &RpcClient, + config: &CliConfig, + program_location: &str, + buffer: Option, + use_deprecated_loader: bool, + use_upgradeable_loader: bool, + upgrade_authority: Option, + max_len: Option, + allow_excessive_balance: bool, + new_keypair: Keypair, +) -> ProcessResult { + let buffer_signer = if let Some(i) = buffer { + config.signers[i] } else { - bpf_loader::id() + &new_keypair + }; + let program_keypair = Keypair::new(); + + let program_data = read_and_verify_elf(program_location)?; + + // Determine which loader + let (loader_id, program_signer, data_len, minimum_balance) = if use_deprecated_loader { + ( + bpf_loader_deprecated::id(), + buffer_signer, + program_data.len(), + rpc_client.get_minimum_balance_for_rent_exemption(program_data.len())?, + ) + } else if use_upgradeable_loader { + let data_len = if let Some(len) = max_len { + len + } else { + program_data.len() * 2 + }; + ( + bpf_loader_upgradeable::id(), + &program_keypair as &dyn Signer, + data_len, + rpc_client.get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::programdata_len(data_len)?, + )?, + ) + } else { + ( + bpf_loader::id(), + buffer_signer, + program_data.len(), + rpc_client.get_minimum_balance_for_rent_exemption(program_data.len())?, + ) }; - let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(program_data.len())?; - let signers = [config.signers[0], program_id]; + // Build messages to calculate fees + let mut messages: Vec<&Message> = Vec::new(); - // Check program account to see if partial initialization has occurred + // 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(&program_id.pubkey(), config.commitment)? + .get_account_with_commitment(&buffer_signer.pubkey(), config.commitment)? .value { - let mut instructions: Vec = vec![]; - let mut balance_needed = 0; - if account.executable { - return Err(CliError::DynamicProgramError( - "Program account is already executable".to_string(), - ) - .into()); - } - if account.owner != loader_id && !system_program::check_id(&account.owner) { - return Err(CliError::DynamicProgramError( - "Program account is already owned by another account".to_string(), - ) - .into()); - } - - if account.data.is_empty() && system_program::check_id(&account.owner) { - instructions.push(system_instruction::allocate( - &program_id.pubkey(), - program_data.len() as u64, - )); - if account.owner != loader_id { - instructions.push(system_instruction::assign(&program_id.pubkey(), &loader_id)); - } - } - if account.lamports < minimum_balance { - let balance = minimum_balance - account.lamports; - instructions.push(system_instruction::transfer( + let account_data_len = if loader_id == bpf_loader_upgradeable::id() { + UpgradeableLoaderState::buffer_len(data_len)? + } else { + data_len + }; + complete_partial_program_init( + &loader_id, + &config.signers[0].pubkey(), + &buffer_signer.pubkey(), + &account, + account_data_len, + minimum_balance, + allow_excessive_balance, + )? + } else if loader_id == bpf_loader_upgradeable::id() { + ( + bpf_loader_upgradeable::create_buffer( &config.signers[0].pubkey(), - &program_id.pubkey(), - balance, - )); - balance_needed = balance; - } else if account.lamports > minimum_balance - && system_program::check_id(&account.owner) - && !allow_excessive_balance - { - return Err(CliError::DynamicProgramError(format!( - "Program account has a balance: {:?}; it may already be in use", - Sol(account.lamports) - )) - .into()); - } - (instructions, balance_needed) + &buffer_signer.pubkey(), + minimum_balance, + data_len, + )?, + minimum_balance, + ) } else { ( vec![system_instruction::create_account( &config.signers[0].pubkey(), - &program_id.pubkey(), + &buffer_signer.pubkey(), minimum_balance, - program_data.len() as u64, + data_len as u64, &loader_id, )], minimum_balance, @@ -1319,23 +1551,28 @@ fn do_process_deploy( } else { None }; - - // Build transactions to calculate fees - let mut messages: Vec<&Message> = Vec::new(); - if let Some(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 = loader_instruction::write( - &program_id.pubkey(), - &loader_id, - (i * DATA_CHUNK_SIZE) as u32, - chunk.to_vec(), - ); - let message = Message::new(&[instruction], Some(&signers[0].pubkey())); + 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(), + ) + } else { + loader_instruction::write( + &buffer_signer.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_message_refs = vec![]; @@ -1344,80 +1581,237 @@ fn do_process_deploy( } messages.append(&mut write_message_refs); - let instruction = loader_instruction::finalize(&program_id.pubkey(), &loader_id); - let finalize_message = Message::new(&[instruction], Some(&signers[0].pubkey())); - messages.push(&finalize_message); + // 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()?, + )?, + data_len, + )?, + Some(&config.signers[0].pubkey()), + ) + } else { + Message::new( + &[loader_instruction::finalize( + &buffer_signer.pubkey(), + &loader_id, + )], + Some(&config.signers[0].pubkey()), + ) + }; + messages.push(&final_message); - let (blockhash, fee_calculator, _) = rpc_client - .get_recent_blockhash_with_commitment(config.commitment)? - .value; - - check_account_for_spend_multiple_fees_with_commitment( + check_payer(rpc_client, config, balance_needed, &messages)?; + send_deploy_messages( rpc_client, - &config.signers[0].pubkey(), - balance_needed, - &fee_calculator, - &messages, - config.commitment, + config, + &initial_message, + &write_messages, + &final_message, + buffer_signer, + program_signer, )?; - if let Some(message) = initial_message { - trace!("Creating or modifying program account"); - let num_required_signatures = message.header.num_required_signatures; + Ok(json!({ + "programId": format!("{}", program_signer.pubkey()), + }) + .to_string()) +} - let mut initial_transaction = Transaction::new_unsigned(message); - // 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 num_required_signatures == 2 { - initial_transaction.try_sign(&signers, blockhash)?; - } else { - initial_transaction.try_sign(&[signers[0]], blockhash)?; - } - let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config( - &initial_transaction, - config.commitment, - config.send_transaction_config, +fn create_ephemeral_keypair( +) -> Result<(usize, bip39::Mnemonic, Keypair), Box> { + const WORDS: usize = 12; + let mnemonic = Mnemonic::new(MnemonicType::for_word_count(WORDS)?, Language::English); + let seed = Seed::new(&mnemonic, ""); + let new_keypair = keypair_from_seed(seed.as_bytes())?; + + Ok((WORDS, mnemonic, new_keypair)) +} + +fn report_ephemeral_mnemonic(words: usize, mnemonic: bip39::Mnemonic) { + let phrase: &str = mnemonic.phrase(); + let divider = String::from_utf8(vec![b'='; phrase.len()]).unwrap(); + eprintln!( + "{}\nTo resume a failed upgrade, recover the ephemeral keypair file with", + divider + ); + eprintln!( + "`solana-keygen recover` and the following {}-word seed phrase,", + words + ); + eprintln!( + "then pass it as the [BUFFER_SIGNER] argument to `solana upgrade ...`\n{}\n{}\n{}", + divider, phrase, divider + ); +} + +fn process_program_upgrade( + rpc_client: &RpcClient, + config: &CliConfig, + program_location: &str, + program: Pubkey, + upgrade_authority: SignerIndex, + buffer: Option, +) -> ProcessResult { + // Create ephemeral keypair to use for Buffer account, if not provided + let (words, mnemonic, new_keypair) = create_ephemeral_keypair()?; + let result = do_process_program_upgrade( + rpc_client, + config, + program_location, + program, + upgrade_authority, + buffer, + new_keypair, + ); + if result.is_err() && buffer.is_none() { + report_ephemeral_mnemonic(words, mnemonic); + } + result +} + +fn do_process_program_upgrade( + rpc_client: &RpcClient, + config: &CliConfig, + program_location: &str, + program: Pubkey, + upgrade_authority: SignerIndex, + buffer: Option, + new_keypair: Keypair, +) -> ProcessResult { + let buffer_signer = if let Some(i) = buffer { + config.signers[i] + } else { + &new_keypair + }; + let upgrade_authority = config.signers[upgrade_authority]; + + let program_data = read_and_verify_elf(program_location)?; + + let loader_id = bpf_loader_upgradeable::id(); + let data_len = program_data.len(); + let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::programdata_len(data_len)?, + )?; + + // 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 { + 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(), ); - log_instruction_custom_error::(result, &config).map_err(|err| { - CliError::DynamicProgramError(format!("Program account allocation failed: {}", err)) - })?; + let message = Message::new(&[instruction], Some(&config.signers[0].pubkey())); + write_messages.push(message); } - - 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.into_iter() { - let mut tx = Transaction::new_unsigned(message); - tx.try_sign(&signers, blockhash)?; - write_transactions.push(tx); + let mut write_message_refs = vec![]; + for message in write_messages.iter() { + write_message_refs.push(message); } + messages.append(&mut write_message_refs); - trace!("Writing program data"); - send_and_confirm_transactions_with_spinner( - &rpc_client, - write_transactions, - &signers, - config.commitment, - last_valid_slot, - ) - .map_err(|err| { - CliError::DynamicProgramError(format!("Data writes to program account failed: {}", err)) - })?; + // Create and add final message + let final_message = Message::new( + &[bpf_loader_upgradeable::upgrade( + &program, + &buffer_signer.pubkey(), + &upgrade_authority.pubkey(), + &config.signers[0].pubkey(), + )], + Some(&config.signers[0].pubkey()), + ); + messages.push(&final_message); + check_payer(rpc_client, config, balance_needed, &messages)?; + send_deploy_messages( + rpc_client, + config, + &initial_message, + &write_messages, + &final_message, + buffer_signer, + upgrade_authority, + )?; + + Ok(json!({ + "programId": format!("{}", program), + }) + .to_string()) +} + +fn process_set_program_upgrade_authority( + rpc_client: &RpcClient, + config: &CliConfig, + program: Pubkey, + upgrade_authority: SignerIndex, + new_upgrade_authority: Option, +) -> ProcessResult { + let upgrade_authority_signer = config.signers[upgrade_authority]; + + trace!("Set a new program upgrade authority"); let (blockhash, _, _) = rpc_client .get_recent_blockhash_with_commitment(config.commitment)? .value; - let mut finalize_tx = Transaction::new_unsigned(finalize_message); - finalize_tx.try_sign(&signers, blockhash)?; - trace!("Finalizing program account"); + let mut tx = Transaction::new_unsigned(Message::new( + &[bpf_loader_upgradeable::set_authority( + &program, + &upgrade_authority_signer.pubkey(), + new_upgrade_authority.as_ref(), + )], + Some(&config.signers[0].pubkey()), + )); + tx.try_sign(&[config.signers[0], upgrade_authority_signer], blockhash)?; rpc_client .send_and_confirm_transaction_with_spinner_and_config( - &finalize_tx, + &tx, config.commitment, RpcSendTransactionConfig { skip_preflight: true, @@ -1425,13 +1819,19 @@ fn do_process_deploy( }, ) .map_err(|e| { - CliError::DynamicProgramError(format!("Finalizing program account failed: {}", e)) + CliError::DynamicProgramError(format!("Setting upgrade authority failed: {}", e)) })?; - Ok(json!({ - "programId": format!("{}", program_id.pubkey()), - }) - .to_string()) + match new_upgrade_authority { + Some(address) => Ok(json!({ + "UpgradeAuthority": format!("{:?}", address), + }) + .to_string()), + None => Ok(json!({ + "UpgradeAuthority": "None", + }) + .to_string()), + } } #[allow(clippy::too_many_arguments)] @@ -1690,20 +2090,54 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Program Deployment // Deploy a custom program to the chain - CliCommand::Deploy { + CliCommand::ProgramDeploy { program_location, - address, + buffer, use_deprecated_loader, + use_upgradeable_loader, + upgrade_authority, + max_len, allow_excessive_balance, - } => process_deploy( + } => process_program_deploy( &rpc_client, config, program_location, - *address, + *buffer, *use_deprecated_loader, + *use_upgradeable_loader, + *upgrade_authority, + *max_len, *allow_excessive_balance, ), + // Upgrade a deployed program on the chain + CliCommand::ProgramUpgrade { + program_location, + program, + upgrade_authority, + buffer, + } => process_program_upgrade( + &rpc_client, + config, + program_location, + *program, + *upgrade_authority, + *buffer, + ), + + // Set a new program upgrade authority + CliCommand::SetProgramUpgradeAuthority { + program, + upgrade_authority, + new_upgrade_authority, + } => process_set_program_upgrade_authority( + &rpc_client, + config, + *program, + *upgrade_authority, + *new_upgrade_authority, + ), + // Stake Commands // Create stake account @@ -2305,7 +2739,8 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' ), ) .subcommand( - SubCommand::with_name("deploy") + SubCommand::with_name("program-deploy") + .visible_alias("deploy") .about("Deploy a program") .arg( Arg::with_name("program_location") @@ -2313,23 +2748,44 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .value_name("PROGRAM_FILEPATH") .takes_value(true) .required(true) - .help("/path/to/program.o"), + .help("/path/to/program.so"), ) .arg( - Arg::with_name("address_signer") + Arg::with_name("buffer_signer") .index(2) - .value_name("PROGRAM_ADDRESS_SIGNER") + .value_name("BUFFER_SIGNER") .takes_value(true) .validator(is_valid_signer) - .help("The signer for the desired address of the program [default: new random address]") + .help("The intermediate buffer account to write data to, can be used to resume a failed deploy [default: new random address]") ) .arg( Arg::with_name("use_deprecated_loader") .long("use-deprecated-loader") .takes_value(false) .hidden(true) // Don't document this argument to discourage its use + .conflicts_with("upgradeable") .help("Use the deprecated BPF loader") ) + .arg( + Arg::with_name("use_upgradeable_loader") + .long("upgradeable") + .help("Use the upgradeable loader with an optional upgrade authority") + ) + .arg( + Arg::with_name("upgrade_authority") + .long("upgrade-authority") + .value_name("UPGRADE_AUTHORITY_PUBKEY") + .takes_value(true) + .help("Address of the upgrade authority, if not specified the program will not 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's to twice the length of the original if not specified") + ) .arg( Arg::with_name("allow_excessive_balance") .long("allow-excessive-deploy-account-balance") @@ -2338,6 +2794,73 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' ) .arg(commitment_arg_with_default("max")), ) + .subcommand( + SubCommand::with_name("program-upgrade") + .about("Upgrade a program") + .arg( + Arg::with_name("program_id") + .index(1) + .value_name("PROGRAM_ADDRESS") + .takes_value(true) + .required(true) + .help("Public key of the program to upgrade") + ) + .arg( + Arg::with_name("upgrade_authority") + .index(2) + .value_name("AUTHORITY_SIGNER") + .takes_value(true) + .required(true) + .validator(is_valid_signer) + .help("Signer who has the authority to upgrade the program") + ) + .arg( + Arg::with_name("program_location") + .index(3) + .value_name("PROGRAM_FILEPATH") + .takes_value(true) + .required(true) + .help("/path/to/program.so"), + ) + .arg( + Arg::with_name("buffer_signer") + .index(4) + .value_name("BUFFER_SIGNER") + .takes_value(true) + .validator(is_valid_signer) + .help("The intermediate buffer account to write data to, can be used to resume a failed deploy [default: new random address]") + ) + .arg(commitment_arg_with_default("max")), + ) + .subcommand( + SubCommand::with_name("program-set-upgrade-authority") + .about("Set a new authority for an upgradeable program") + .arg( + Arg::with_name("program_id") + .index(1) + .value_name("PROGRAM_ADDRESS") + .takes_value(true) + .required(true) + .help("Public key of the program to upgrade") + ) + .arg( + Arg::with_name("upgrade_authority") + .index(2) + .value_name("AUTHORITY_SIGNER") + .takes_value(true) + .required(true) + .validator(is_valid_signer) + .help("Signer who has the authority to upgrade the program") + ) + .arg( + Arg::with_name("new_upgrade_authority") + .long("new-upgrade-authority") + .value_name("NEW_UPGRADE_AUTHORITY_PUBKEY") + .takes_value(true) + .help("Address of the upgrade authority, if not specified the program will not upgradeable") + ) + .arg(commitment_arg_with_default("max")), + ) .subcommand( SubCommand::with_name("pay") .about("Deprecated alias for the transfer command") @@ -2693,18 +3216,22 @@ mod tests { ); // Test Deploy Subcommand - let test_deploy = - test_commands - .clone() - .get_matches_from(vec!["test", "deploy", "/Users/test/program.o"]); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "deploy", + "/Users/test/program.so", + ]); assert_eq!( parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::Deploy { - program_location: "/Users/test/program.o".to_string(), - address: None, + command: CliCommand::ProgramDeploy { + program_location: "/Users/test/program.so".to_string(), + buffer: None, use_deprecated_loader: false, + use_upgradeable_loader: false, allow_excessive_balance: false, + upgrade_authority: None, + max_len: None, }, signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -2716,17 +3243,20 @@ mod tests { let test_deploy = test_commands.clone().get_matches_from(vec![ "test", "deploy", - "/Users/test/program.o", + "/Users/test/program.so", &custom_address_file, ]); assert_eq!( parse_command(&test_deploy, &default_signer, &mut None).unwrap(), CliCommandInfo { - command: CliCommand::Deploy { - program_location: "/Users/test/program.o".to_string(), - address: Some(1), + command: CliCommand::ProgramDeploy { + program_location: "/Users/test/program.so".to_string(), + buffer: Some(1), use_deprecated_loader: false, + use_upgradeable_loader: false, allow_excessive_balance: false, + upgrade_authority: None, + max_len: None, }, signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), @@ -2735,6 +3265,143 @@ mod tests { } ); + let custom_address = Keypair::new(); + let custom_address_file = make_tmp_path("custom_address_file"); + write_keypair_file(&custom_address, &custom_address_file).unwrap(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program-deploy", + "/Users/test/program.so", + &custom_address_file, + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::ProgramDeploy { + program_location: "/Users/test/program.so".to_string(), + buffer: Some(1), + use_deprecated_loader: false, + use_upgradeable_loader: false, + allow_excessive_balance: false, + upgrade_authority: None, + max_len: None, + }, + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&custom_address_file).unwrap().into(), + ], + } + ); + + let upgrade_authority = Pubkey::new_unique(); + let test_deploy = test_commands.clone().get_matches_from(vec![ + "test", + "program-deploy", + "/Users/test/program.so", + "--upgradeable", + "--upgrade-authority", + &upgrade_authority.to_string(), + "--max-len", + "42", + ]); + assert_eq!( + parse_command(&test_deploy, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::ProgramDeploy { + program_location: "/Users/test/program.so".to_string(), + buffer: None, + use_deprecated_loader: false, + use_upgradeable_loader: true, + allow_excessive_balance: false, + upgrade_authority: Some(upgrade_authority), + max_len: Some(42), + }, + signers: vec![read_keypair_file(&keypair_file).unwrap().into()], + } + ); + + // Test Upgrade Subcommand + let program_pubkey = Pubkey::new_unique(); + let upgrade_address = Keypair::new(); + let upgrade_address_file = make_tmp_path("upgrade_address_file"); + write_keypair_file(&upgrade_address, &upgrade_address_file).unwrap(); + let test = test_commands.clone().get_matches_from(vec![ + "test", + "program-upgrade", + &program_pubkey.to_string(), + &upgrade_address_file, + "/Users/test/program.so", + ]); + assert_eq!( + parse_command(&test, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::ProgramUpgrade { + program_location: "/Users/test/program.so".to_string(), + program: program_pubkey, + buffer: None, + upgrade_authority: 1, + }, + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&upgrade_address_file).unwrap().into() + ], + } + ); + + // Test SetProgramUpgradeAuthority Subcommand + let new_upgrade_authority = Pubkey::new_unique(); + let program_pubkey = 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 test = test_commands.clone().get_matches_from(vec![ + "test", + "program-set-upgrade-authority", + &program_pubkey.to_string(), + &authority_address_file, + "--new-upgrade-authority", + &new_upgrade_authority.to_string(), + ]); + assert_eq!( + parse_command(&test, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::SetProgramUpgradeAuthority { + program: program_pubkey, + upgrade_authority: 1, + new_upgrade_authority: Some(new_upgrade_authority), + }, + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&authority_address_file).unwrap().into() + ], + } + ); + + let program_pubkey = 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 test = test_commands.clone().get_matches_from(vec![ + "test", + "program-set-upgrade-authority", + &program_pubkey.to_string(), + &authority_address_file, + ]); + assert_eq!( + parse_command(&test, &default_signer, &mut None).unwrap(), + CliCommandInfo { + command: CliCommand::SetProgramUpgradeAuthority { + program: program_pubkey, + upgrade_authority: 1, + new_upgrade_authority: None, + }, + signers: vec![ + read_keypair_file(&keypair_file).unwrap().into(), + read_keypair_file(&authority_address_file).unwrap().into() + ], + } + ); + // Test ResolveSigner Subcommand, KeypairUrl::Filepath let test_resolve_signer = test_commands @@ -3038,11 +3705,14 @@ mod tests { let default_keypair = Keypair::new(); config.signers = vec![&default_keypair]; - config.command = CliCommand::Deploy { + config.command = CliCommand::ProgramDeploy { program_location: pathbuf.to_str().unwrap().to_string(), - address: None, + buffer: None, use_deprecated_loader: false, + use_upgradeable_loader: false, allow_excessive_balance: false, + upgrade_authority: None, + max_len: None, }; let result = process_command(&config); let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); @@ -3057,15 +3727,192 @@ mod tests { assert!(program_id.parse::().is_ok()); // Failure case - config.command = CliCommand::Deploy { + config.command = CliCommand::ProgramDeploy { program_location: "bad/file/location.so".to_string(), - address: None, + buffer: None, use_deprecated_loader: false, + use_upgradeable_loader: false, allow_excessive_balance: false, + upgrade_authority: None, + max_len: None, }; assert!(process_command(&config).is_err()); } + #[test] + fn test_cli_deploy_upgradeable() { + solana_logger::setup(); + + let upgrade_authority = Pubkey::new_unique(); + let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + pathbuf.push("tests"); + pathbuf.push("fixtures"); + pathbuf.push("noop"); + pathbuf.set_extension("so"); + + // Success case + let mut config = CliConfig::default(); + let account_info_response = json!(Response { + context: RpcResponseContext { slot: 1 }, + value: Value::Null, + }); + let mut mocks = HashMap::new(); + mocks.insert(RpcRequest::GetAccountInfo, account_info_response); + let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); + + config.rpc_client = Some(rpc_client); + let default_keypair = Keypair::new(); + config.signers = vec![&default_keypair]; + + config.command = CliCommand::ProgramDeploy { + program_location: pathbuf.to_str().unwrap().to_string(), + buffer: None, + use_deprecated_loader: false, + use_upgradeable_loader: true, + allow_excessive_balance: false, + upgrade_authority: Some(upgrade_authority), + max_len: None, + }; + let result = process_command(&config); + let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); + let program_id = json + .as_object() + .unwrap() + .get("programId") + .unwrap() + .as_str() + .unwrap(); + + assert!(program_id.parse::().is_ok()); + + // Failure case + config.command = CliCommand::ProgramDeploy { + program_location: "bad/file/location.so".to_string(), + buffer: None, + use_deprecated_loader: false, + use_upgradeable_loader: true, + allow_excessive_balance: false, + upgrade_authority: Some(upgrade_authority), + max_len: None, + }; + assert!(process_command(&config).is_err()); + } + + #[test] + fn test_cli_upgrade() { + solana_logger::setup(); + + let upgrade_authority = Keypair::new(); + let program = Pubkey::new_unique(); + let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + pathbuf.push("tests"); + pathbuf.push("fixtures"); + pathbuf.push("noop"); + pathbuf.set_extension("so"); + + // Success case + let mut config = CliConfig::default(); + let account_info_response = json!(Response { + context: RpcResponseContext { slot: 1 }, + value: Value::Null, + }); + let mut mocks = HashMap::new(); + mocks.insert(RpcRequest::GetAccountInfo, account_info_response); + let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); + + config.rpc_client = Some(rpc_client); + let default_keypair = Keypair::new(); + config.signers = vec![&default_keypair, &upgrade_authority]; + + config.command = CliCommand::ProgramUpgrade { + program_location: pathbuf.to_str().unwrap().to_string(), + program, + buffer: None, + upgrade_authority: 1, + }; + let result = process_command(&config); + let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); + let program_id = json + .as_object() + .unwrap() + .get("programId") + .unwrap() + .as_str() + .unwrap(); + + assert!(program_id.parse::().is_ok()); + + // Failure case + config.command = CliCommand::ProgramUpgrade { + program_location: "bad/file/location.so".to_string(), + program, + buffer: None, + upgrade_authority: 1, + }; + assert!(process_command(&config).is_err()); + } + + #[test] + fn test_cli_set_upgrade_authority() { + solana_logger::setup(); + + let program = Pubkey::new_unique(); + let upgrade_authority = Keypair::new(); + let new_upgrade_authority = Pubkey::new_unique(); + let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + pathbuf.push("tests"); + pathbuf.push("fixtures"); + pathbuf.push("noop"); + pathbuf.set_extension("so"); + + // Success case + let mut config = CliConfig::default(); + let account_info_response = json!(Response { + context: RpcResponseContext { slot: 1 }, + value: Value::Null, + }); + let mut mocks = HashMap::new(); + mocks.insert(RpcRequest::GetAccountInfo, account_info_response); + let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks); + + config.rpc_client = Some(rpc_client); + let default_keypair = Keypair::new(); + config.signers = vec![&default_keypair, &upgrade_authority]; + + config.command = CliCommand::SetProgramUpgradeAuthority { + program, + upgrade_authority: 1, + new_upgrade_authority: Some(new_upgrade_authority), + }; + let result = process_command(&config); + let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); + println!("json {:?}", json); + let program_id = json + .as_object() + .unwrap() + .get("UpgradeAuthority") + .unwrap() + .as_str() + .unwrap(); + assert!(program_id.parse::().is_ok()); + + config.command = CliCommand::SetProgramUpgradeAuthority { + program, + upgrade_authority: 1, + new_upgrade_authority: None, + }; + let result = process_command(&config); + let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); + let program_id = json + .as_object() + .unwrap() + .get("UpgradeAuthority") + .unwrap() + .as_str() + .unwrap(); + assert_eq!(program_id, "None"); + } + #[test] fn test_parse_transfer_subcommand() { let test_commands = app("test", "desc", "version"); diff --git a/cli/tests/deploy.rs b/cli/tests/deploy.rs index 0f430e74b6..541c7e2a99 100644 --- a/cli/tests/deploy.rs +++ b/cli/tests/deploy.rs @@ -5,6 +5,7 @@ use solana_core::test_validator::TestValidator; use solana_faucet::faucet::run_local_faucet; use solana_sdk::{ bpf_loader, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, commitment_config::CommitmentConfig, pubkey::Pubkey, signature::{Keypair, Signer}, @@ -49,11 +50,14 @@ fn test_cli_deploy_program() { config.signers = vec![&keypair]; process_command(&config).unwrap(); - config.command = CliCommand::Deploy { + config.command = CliCommand::ProgramDeploy { program_location: pathbuf.to_str().unwrap().to_string(), - address: None, + buffer: None, use_deprecated_loader: false, + use_upgradeable_loader: false, allow_excessive_balance: false, + upgrade_authority: None, + max_len: None, }; let response = process_command(&config); @@ -84,11 +88,14 @@ fn test_cli_deploy_program() { // Test custom address let custom_address_keypair = Keypair::new(); config.signers = vec![&keypair, &custom_address_keypair]; - config.command = CliCommand::Deploy { + config.command = CliCommand::ProgramDeploy { program_location: pathbuf.to_str().unwrap().to_string(), - address: Some(1), + buffer: Some(1), use_deprecated_loader: false, + use_upgradeable_loader: false, allow_excessive_balance: false, + upgrade_authority: None, + max_len: None, }; process_command(&config).unwrap(); let account1 = rpc_client @@ -116,20 +123,26 @@ fn test_cli_deploy_program() { process_command(&config).unwrap(); config.signers = vec![&keypair, &custom_address_keypair]; - config.command = CliCommand::Deploy { + config.command = CliCommand::ProgramDeploy { program_location: pathbuf.to_str().unwrap().to_string(), - address: Some(1), + buffer: Some(1), use_deprecated_loader: false, + use_upgradeable_loader: false, allow_excessive_balance: false, + upgrade_authority: None, + max_len: None, }; process_command(&config).unwrap_err(); // Use forcing parameter to deploy to account with excess balance - config.command = CliCommand::Deploy { + config.command = CliCommand::ProgramDeploy { program_location: pathbuf.to_str().unwrap().to_string(), - address: Some(1), + buffer: Some(1), use_deprecated_loader: false, + use_upgradeable_loader: false, allow_excessive_balance: true, + upgrade_authority: None, + max_len: None, }; process_command(&config).unwrap(); let account2 = rpc_client @@ -142,3 +155,268 @@ fn test_cli_deploy_program() { assert_eq!(account2.executable, true); assert_eq!(account0.data, account2.data); } + +#[test] +fn test_cli_deploy_upgradeable_program() { + solana_logger::setup(); + + let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + pathbuf.push("tests"); + pathbuf.push("fixtures"); + pathbuf.push("noop"); + pathbuf.set_extension("so"); + + let mint_keypair = Keypair::new(); + let 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(); + println!( + "max_len {:?} {:?}", + max_len, + UpgradeableLoaderState::programdata_len(max_len) + ); + let minimum_balance_for_programdata = rpc_client + .get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::programdata_len(max_len).unwrap(), + ) + .unwrap(); + let minimum_balance_for_program = rpc_client + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::program_len().unwrap()) + .unwrap(); + let upgrade_authority = Keypair::new(); + + let mut config = CliConfig::recent_for_tests(); + let keypair = Keypair::new(); + config.json_rpc_url = test_validator.rpc_url(); + config.command = CliCommand::Airdrop { + faucet_host: None, + faucet_port: faucet_addr.port(), + pubkey: None, + lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program, + }; + config.signers = vec![&keypair]; + process_command(&config).unwrap(); + + // Deploy and attempt to upgrade a non-upgradeable program + config.command = CliCommand::ProgramDeploy { + program_location: pathbuf.to_str().unwrap().to_string(), + buffer: None, + use_deprecated_loader: false, + use_upgradeable_loader: true, + allow_excessive_balance: false, + upgrade_authority: None, + 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(); + + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::ProgramUpgrade { + program_location: pathbuf.to_str().unwrap().to_string(), + program: program_id, + buffer: None, + upgrade_authority: 1, + }; + process_command(&config).unwrap_err(); + + // Deploy the upgradeable program + config.command = CliCommand::ProgramDeploy { + program_location: pathbuf.to_str().unwrap().to_string(), + buffer: None, + use_deprecated_loader: false, + use_upgradeable_loader: true, + allow_excessive_balance: false, + upgrade_authority: Some(upgrade_authority.pubkey()), + 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(); + let program_account = rpc_client + .get_account_with_commitment(&program_id, CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + assert_eq!(program_account.lamports, minimum_balance_for_program); + 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()); + let programdata_account = rpc_client + .get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + assert_eq!( + programdata_account.lamports, + minimum_balance_for_programdata + ); + assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id()); + assert_eq!(programdata_account.executable, false); + assert_eq!( + programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..], + program_data[..] + ); + + // Upgrade the program + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::ProgramUpgrade { + program_location: pathbuf.to_str().unwrap().to_string(), + program: program_id, + buffer: None, + upgrade_authority: 1, + }; + let response = process_command(&config); + let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); + let program_id_str = json + .as_object() + .unwrap() + .get("programId") + .unwrap() + .as_str() + .unwrap(); + let program_id = Pubkey::from_str(&program_id_str).unwrap(); + let program_account = rpc_client + .get_account_with_commitment(&program_id, CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + assert_eq!(program_account.lamports, minimum_balance_for_program); + 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()); + let programdata_account = rpc_client + .get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + assert_eq!( + programdata_account.lamports, + minimum_balance_for_programdata + ); + assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id()); + assert_eq!(programdata_account.executable, false); + assert_eq!( + programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..], + program_data[..] + ); + + // Set a new authority + let new_upgrade_authority = Keypair::new(); + config.signers = vec![&keypair, &upgrade_authority]; + config.command = CliCommand::SetProgramUpgradeAuthority { + program: program_id, + upgrade_authority: 1, + new_upgrade_authority: Some(new_upgrade_authority.pubkey()), + }; + let response = process_command(&config); + let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); + let new_upgrade_authority_str = json + .as_object() + .unwrap() + .get("UpgradeAuthority") + .unwrap() + .as_str() + .unwrap(); + assert_eq!( + Pubkey::from_str(&new_upgrade_authority_str).unwrap(), + new_upgrade_authority.pubkey() + ); + + // Upgrade with new authority + config.signers = vec![&keypair, &new_upgrade_authority]; + config.command = CliCommand::ProgramUpgrade { + program_location: pathbuf.to_str().unwrap().to_string(), + program: program_id, + buffer: None, + upgrade_authority: 1, + }; + let response = process_command(&config); + let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); + let program_id_str = json + .as_object() + .unwrap() + .get("programId") + .unwrap() + .as_str() + .unwrap(); + let program_id = Pubkey::from_str(&program_id_str).unwrap(); + let program_account = rpc_client + .get_account_with_commitment(&program_id, CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + assert_eq!(program_account.lamports, minimum_balance_for_program); + 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()); + let programdata_account = rpc_client + .get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + assert_eq!( + programdata_account.lamports, + minimum_balance_for_programdata + ); + assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id()); + assert_eq!(programdata_account.executable, false); + assert_eq!( + programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..], + program_data[..] + ); + + // Set a no authority + config.signers = vec![&keypair, &new_upgrade_authority]; + config.command = CliCommand::SetProgramUpgradeAuthority { + program: program_id, + upgrade_authority: 1, + new_upgrade_authority: None, + }; + let response = process_command(&config); + let json: Value = serde_json::from_str(&response.unwrap()).unwrap(); + let new_upgrade_authority_str = json + .as_object() + .unwrap() + .get("UpgradeAuthority") + .unwrap() + .as_str() + .unwrap(); + assert_eq!(new_upgrade_authority_str, "None"); + + // Upgrade with no authority + config.signers = vec![&keypair, &new_upgrade_authority]; + config.command = CliCommand::ProgramUpgrade { + program_location: pathbuf.to_str().unwrap().to_string(), + program: program_id, + buffer: None, + upgrade_authority: 1, + }; + process_command(&config).unwrap_err(); +} diff --git a/ledger/src/builtins.rs b/ledger/src/builtins.rs index 6151ae6f38..a35bbdd0d4 100644 --- a/ledger/src/builtins.rs +++ b/ledger/src/builtins.rs @@ -20,6 +20,11 @@ fn genesis_builtins(cluster_type: ClusterType, bpf_jit: bool) -> Vec { } else { to_builtin!(solana_bpf_loader_program!()) }, + if bpf_jit { + to_builtin!(solana_bpf_loader_upgradeable_program_with_jit!()) + } else { + to_builtin!(solana_bpf_loader_upgradeable_program!()) + }, ] } else { // Remove this `else` block and the `cluster_type` argument to this function once @@ -30,11 +35,18 @@ fn genesis_builtins(cluster_type: ClusterType, bpf_jit: bool) -> Vec { /// Builtin programs activated dynamically by feature fn feature_builtins() -> Vec<(Builtin, Pubkey, ActivationType)> { - vec![( - to_builtin!(solana_bpf_loader_program!()), - feature_set::bpf_loader2_program::id(), - ActivationType::NewProgram, - )] + vec![ + ( + to_builtin!(solana_bpf_loader_program!()), + feature_set::bpf_loader2_program::id(), + ActivationType::NewProgram, + ), + ( + to_builtin!(solana_bpf_loader_upgradeable_program!()), + feature_set::bpf_loader_upgradeable_program::id(), + ActivationType::NewProgram, + ), + ] } pub(crate) fn get(cluster_type: ClusterType, bpf_jit: bool) -> Builtins { diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 21e32c9bb7..6cbad7e774 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2110,6 +2110,20 @@ dependencies = [ "solana-program", ] +[[package]] +name = "solana-bpf-rust-upgradeable" +version = "1.5.0" +dependencies = [ + "solana-program", +] + +[[package]] +name = "solana-bpf-rust-upgraded" +version = "1.5.0" +dependencies = [ + "solana-program", +] + [[package]] name = "solana-config-program" version = "1.5.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 8391b04431..f266f4cebf 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -68,6 +68,8 @@ members = [ "rust/spoof1", "rust/spoof1_system", "rust/sysval", + "rust/upgradeable", + "rust/upgraded", ] [package.metadata.docs.rs] diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index f284d589f7..971b21f0d3 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -87,6 +87,8 @@ fn main() { "spoof1", "spoof1_system", "sysval", + "upgradeable", + "upgraded", ]; for program in rust_programs.iter() { println!( diff --git a/programs/bpf/rust/upgradeable/Cargo.toml b/programs/bpf/rust/upgradeable/Cargo.toml new file mode 100644 index 0000000000..aa6fdf315f --- /dev/null +++ b/programs/bpf/rust/upgradeable/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solana-bpf-rust-upgradeable" +version = "1.5.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "1.5.0" } + +[lib] +name = "solana_bpf_rust_upgradeable" +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/upgradeable/Xargo.toml b/programs/bpf/rust/upgradeable/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/upgradeable/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/upgradeable/src/lib.rs b/programs/bpf/rust/upgradeable/src/lib.rs new file mode 100644 index 0000000000..c693151943 --- /dev/null +++ b/programs/bpf/rust/upgradeable/src/lib.rs @@ -0,0 +1,16 @@ +//! @brief Example Rust-based BPF upgradeable program + +extern crate solana_program; +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("Upgradeable program"); + Err(42.into()) +} diff --git a/programs/bpf/rust/upgraded/Cargo.toml b/programs/bpf/rust/upgraded/Cargo.toml new file mode 100644 index 0000000000..4af757a419 --- /dev/null +++ b/programs/bpf/rust/upgraded/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "solana-bpf-rust-upgraded" +version = "1.5.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "1.5.0" } + +[lib] +name = "solana_bpf_rust_upgraded" +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/upgraded/Xargo.toml b/programs/bpf/rust/upgraded/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/upgraded/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/upgraded/src/lib.rs b/programs/bpf/rust/upgraded/src/lib.rs new file mode 100644 index 0000000000..3fcf2cae0f --- /dev/null +++ b/programs/bpf/rust/upgraded/src/lib.rs @@ -0,0 +1,16 @@ +//! @brief Example Rust-based BPF upgraded program + +extern crate solana_program; +use solana_program::{ + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("Upgraded program"); + Err(43.into()) +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 2cd9b0cbfe..c11f218cb2 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -14,7 +14,10 @@ use solana_runtime::{ bank::Bank, bank_client::BankClient, genesis_utils::{create_genesis_config, GenesisConfigInfo}, - loader_utils::load_program, + loader_utils::{ + load_buffer_account, load_program, load_upgradeable_program, set_upgrade_authority, + upgrade_program, + }, }; use solana_sdk::{ account::Account, @@ -94,6 +97,44 @@ fn write_bpf_program( } } +fn load_upgradeable_bpf_program( + bank_client: &BankClient, + payer_keypair: &Keypair, + name: &str, +) -> (Pubkey, Keypair) { + let path = create_bpf_path(name); + let mut file = File::open(&path).unwrap_or_else(|err| { + panic!("Failed to open {}: {}", path.display(), err); + }); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + load_upgradeable_program(bank_client, payer_keypair, elf) +} + +fn upgrade_bpf_program( + bank_client: &BankClient, + payer_keypair: &Keypair, + executable_pubkey: &Pubkey, + authority_keypair: &Keypair, + name: &str, +) { + let path = create_bpf_path(name); + let mut file = File::open(&path).unwrap_or_else(|err| { + panic!("Failed to open {}: {}", path.display(), err); + }); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + let buffer_pubkey = load_buffer_account(bank_client, payer_keypair, &elf); + upgrade_program( + bank_client, + payer_keypair, + executable_pubkey, + &buffer_pubkey, + &authority_keypair, + &payer_keypair.pubkey(), + ) +} + fn run_program( name: &str, program_id: &Pubkey, @@ -1382,3 +1423,79 @@ fn test_program_bpf_test_use_latest_executor2() { .send_and_confirm_message(&[&mint_keypair], message) .is_ok()); } + +#[cfg(feature = "bpf_rust")] +#[test] +fn test_program_bpf_upgrade() { + solana_logger::setup(); + + let mut nonce = 0; + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mut bank = Bank::new(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_upgradeable_program!(); + bank.add_builtin(&name, id, entrypoint); + let bank_client = BankClient::new(bank); + + // deploy upgrade program + let (program_id, authority_keypair) = + load_upgradeable_bpf_program(&bank_client, &mint_keypair, "solana_bpf_rust_upgradeable"); + + // call upgrade program + nonce += 1; + let instruction = Instruction::new(program_id, &[nonce], vec![]); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(42)) + ); + + // upgrade program + upgrade_bpf_program( + &bank_client, + &mint_keypair, + &program_id, + &authority_keypair, + "solana_bpf_rust_upgraded", + ); + + // call upgraded program + nonce += 1; + let instruction = Instruction::new(program_id, &[nonce], vec![]); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(43)) + ); + + // upgrade back to the original program + let new_authority_keypair = Keypair::new(); + set_upgrade_authority( + &bank_client, + &mint_keypair, + &program_id, + &authority_keypair, + Some(&new_authority_keypair.pubkey()), + ); + + // upgrade back to the original program + upgrade_bpf_program( + &bank_client, + &mint_keypair, + &program_id, + &new_authority_keypair, + "solana_bpf_rust_upgradeable", + ); + + // call original program + nonce += 1; + let instruction = Instruction::new(program_id, &[nonce], vec![]); + let result = bank_client.send_and_confirm_instruction(&mint_keypair, instruction); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(42)) + ); +} diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 1a4df76057..5c57244815 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -4,6 +4,8 @@ pub mod bpf_verifier; pub mod deprecated; pub mod serialization; pub mod syscalls; +pub mod upgradeable; +pub mod upgradeable_with_jit; pub mod with_jit; use crate::{ @@ -18,17 +20,24 @@ use solana_rbpf::{ memory_region::MemoryRegion, vm::{Config, EbpfVm, Executable, InstructionMeter}, }; +use solana_runtime::message_processor::MessageProcessor; use solana_sdk::{ + account_utils::State, bpf_loader, bpf_loader_deprecated, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, + clock::Clock, decode_error::DecodeError, entrypoint::SUCCESS, feature_set::bpf_compute_budget_balancing, instruction::InstructionError, - keyed_account::{is_executable, next_keyed_account, KeyedAccount}, + keyed_account::{from_keyed_account, next_keyed_account, KeyedAccount}, loader_instruction::LoaderInstruction, + loader_upgradeable_instruction::UpgradeableLoaderInstruction, process_instruction::{stable_log, ComputeMeter, Executor, InvokeContext}, program_utils::limited_deserialize, pubkey::Pubkey, + rent::Rent, + system_instruction, }; use std::{cell::RefCell, fmt::Debug, rc::Rc, sync::Arc}; use thiserror::Error; @@ -93,13 +102,14 @@ fn map_ebpf_error( } pub fn create_and_cache_executor( - program: &KeyedAccount, + key: &Pubkey, + data: &[u8], invoke_context: &mut dyn InvokeContext, use_jit: bool, ) -> Result, InstructionError> { let bpf_compute_budget = invoke_context.get_bpf_compute_budget(); - let mut executable = Executable::::from_elf( - &program.try_account_ref()?.data, + let mut program = Executable::::from_elf( + data, None, Config { max_call_depth: bpf_compute_budget.max_call_depth, @@ -109,7 +119,7 @@ pub fn create_and_cache_executor( }, ) .map_err(|e| map_ebpf_error(invoke_context, e))?; - let (_, elf_bytes) = executable + let (_, elf_bytes) = program .get_text_bytes() .map_err(|e| map_ebpf_error(invoke_context, e))?; bpf_verifier::check( @@ -119,15 +129,41 @@ pub fn create_and_cache_executor( .map_err(|e| map_ebpf_error(invoke_context, EbpfError::UserError(e)))?; let syscall_registry = syscalls::register_syscalls(invoke_context) .map_err(|e| map_ebpf_error(invoke_context, e))?; - executable.set_syscall_registry(syscall_registry); - if use_jit && executable.jit_compile().is_err() { + program.set_syscall_registry(syscall_registry); + if use_jit && program.jit_compile().is_err() { return Err(BPFLoaderError::JustInTimeCompilationFailed.into()); } - let executor = Arc::new(BPFExecutor { executable }); - invoke_context.add_executor(program.unsigned_key(), executor.clone()); + let executor = Arc::new(BPFExecutor { program }); + invoke_context.add_executor(key, executor.clone()); Ok(executor) } +fn write_program_data( + account: &KeyedAccount, + 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 + ); + return Err(InstructionError::AccountDataTooSmall); + } + account.try_account_ref_mut()?.data[offset..offset + len].copy_from_slice(&bytes); + Ok(()) +} + /// Default program heap size, allocators /// are expected to enforce this const DEFAULT_HEAP_SIZE: usize = 32 * 1024; @@ -135,14 +171,14 @@ const DEFAULT_HEAP_SIZE: usize = 32 * 1024; /// Create the BPF virtual machine pub fn create_vm<'a>( loader_id: &'a Pubkey, - executable: &'a dyn Executable, + program: &'a dyn Executable, parameter_bytes: &mut [u8], parameter_accounts: &'a [KeyedAccount<'a>], invoke_context: &'a mut dyn InvokeContext, ) -> Result, EbpfError> { let heap = vec![0_u8; DEFAULT_HEAP_SIZE]; let heap_region = MemoryRegion::new_from_slice(&heap, MM_HEAP_START, 0, true); - let mut vm = EbpfVm::new(executable, parameter_bytes, &[heap_region])?; + let mut vm = EbpfVm::new(program, parameter_bytes, &[heap_region])?; syscalls::bind_syscall_context_objects( loader_id, &mut vm, @@ -153,81 +189,13 @@ pub fn create_vm<'a>( Ok(vm) } -fn process_instruction_general( - program_id: &Pubkey, - keyed_accounts: &[KeyedAccount], - instruction_data: &[u8], - invoke_context: &mut dyn InvokeContext, - use_jit: bool, -) -> Result<(), InstructionError> { - debug_assert!(bpf_loader::check_id(program_id) || bpf_loader_deprecated::check_id(program_id)); - - let logger = invoke_context.get_logger(); - - if keyed_accounts.is_empty() { - log!(logger, "No account keys"); - return Err(InstructionError::NotEnoughAccountKeys); - } - let program = &keyed_accounts[0]; - - if is_executable(keyed_accounts)? { - let executor = match invoke_context.get_executor(program.unsigned_key()) { - Some(executor) => executor, - None => create_and_cache_executor(program, invoke_context, use_jit)?, - }; - executor.execute( - program_id, - keyed_accounts, - instruction_data, - invoke_context, - use_jit, - )? - } else if !keyed_accounts.is_empty() { - match limited_deserialize(instruction_data)? { - LoaderInstruction::Write { offset, bytes } => { - if program.signer_key().is_none() { - log!(logger, "key[0] did not sign the transaction"); - return Err(InstructionError::MissingRequiredSignature); - } - let offset = offset as usize; - let len = bytes.len(); - if program.data_len()? < offset + len { - log!( - logger, - "Write overflow: {} < {}", - program.data_len()?, - offset + len - ); - return Err(InstructionError::AccountDataTooSmall); - } - program.try_account_ref_mut()?.data[offset..offset + len].copy_from_slice(&bytes); - } - LoaderInstruction::Finalize => { - if program.signer_key().is_none() { - log!(logger, "key[0] did not sign the transaction"); - return Err(InstructionError::MissingRequiredSignature); - } - - let _ = create_and_cache_executor(program, invoke_context, use_jit)?; - program.try_account_ref_mut()?.executable = true; - log!( - logger, - "Finalized account {:?}", - program.signer_key().unwrap() - ); - } - } - } - Ok(()) -} - pub fn process_instruction( program_id: &Pubkey, keyed_accounts: &[KeyedAccount], instruction_data: &[u8], invoke_context: &mut dyn InvokeContext, ) -> Result<(), InstructionError> { - process_instruction_general( + process_instruction_common( program_id, keyed_accounts, instruction_data, @@ -242,7 +210,7 @@ pub fn process_instruction_jit( instruction_data: &[u8], invoke_context: &mut dyn InvokeContext, ) -> Result<(), InstructionError> { - process_instruction_general( + process_instruction_common( program_id, keyed_accounts, instruction_data, @@ -251,6 +219,403 @@ pub fn process_instruction_jit( ) } +fn process_instruction_common( + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], + invoke_context: &mut dyn InvokeContext, + use_jit: bool, +) -> Result<(), InstructionError> { + let logger = invoke_context.get_logger(); + + if !(bpf_loader::check_id(program_id) + || bpf_loader_deprecated::check_id(program_id) + || bpf_loader_upgradeable::check_id(program_id)) + { + log!(logger, "Invalid BPF loader id"); + return Err(InstructionError::IncorrectProgramId); + } + + let account_iter = &mut keyed_accounts.iter(); + let first_account = next_keyed_account(account_iter)?; + if first_account.executable()? { + let (program, offset) = if bpf_loader_upgradeable::check_id(program_id) { + if let UpgradeableLoaderState::Program { + programdata_address, + } = first_account.state()? + { + let programdata = next_keyed_account(account_iter)?; + if programdata_address != *programdata.unsigned_key() { + log!(logger, "Wrong ProgramData account for this Program account"); + return Err(InstructionError::InvalidArgument); + } + ( + programdata, + UpgradeableLoaderState::programdata_data_offset()?, + ) + } else { + log!(logger, "Invalid Program account"); + return Err(InstructionError::InvalidAccountData); + } + } else { + (first_account, 0) + }; + + if program.owner()? != *program_id { + log!(logger, "Executable account not owned by the BPF loader"); + return Err(InstructionError::IncorrectProgramId); + } + + let executor = match invoke_context.get_executor(program.unsigned_key()) { + Some(executor) => executor, + None => create_and_cache_executor( + program.unsigned_key(), + &program.try_account_ref()?.data[offset..], + invoke_context, + use_jit, + )?, + }; + executor.execute( + program_id, + keyed_accounts, + instruction_data, + invoke_context, + use_jit, + )? + } else if bpf_loader_upgradeable::check_id(program_id) { + process_loader_upgradeable_instruction( + program_id, + keyed_accounts, + instruction_data, + invoke_context, + use_jit, + )?; + } else { + process_loader_instruction( + program_id, + keyed_accounts, + instruction_data, + invoke_context, + use_jit, + )?; + } + Ok(()) +} + +fn process_loader_upgradeable_instruction( + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], + invoke_context: &mut dyn InvokeContext, + use_jit: bool, +) -> Result<(), InstructionError> { + let logger = invoke_context.get_logger(); + let account_iter = &mut keyed_accounts.iter(); + + match limited_deserialize(instruction_data)? { + UpgradeableLoaderInstruction::InitializeBuffer => { + let buffer = next_keyed_account(account_iter)?; + if UpgradeableLoaderState::Uninitialized != buffer.state()? { + log!(logger, "Buffer account already initialized"); + return Err(InstructionError::AccountAlreadyInitialized); + } + buffer.set_state(&UpgradeableLoaderState::Buffer)?; + } + UpgradeableLoaderInstruction::Write { offset, bytes } => { + let buffer = next_keyed_account(account_iter)?; + if UpgradeableLoaderState::Buffer != buffer.state()? { + log!(logger, "Invalid Buffer account"); + return Err(InstructionError::InvalidAccountData); + } + write_program_data( + buffer, + UpgradeableLoaderState::buffer_data_offset()? + offset as usize, + &bytes, + invoke_context, + )?; + } + UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len } => { + let payer = next_keyed_account(account_iter)?; + let programdata = next_keyed_account(account_iter)?; + let program = next_keyed_account(account_iter)?; + let buffer = next_keyed_account(account_iter)?; + let rent = from_keyed_account::(next_keyed_account(account_iter)?)?; + let clock = from_keyed_account::(next_keyed_account(account_iter)?)?; + let system = next_keyed_account(account_iter)?; + let authority = next_keyed_account(account_iter) + .ok() + .map(|account| account.unsigned_key()); + + // Verify Program account + + if UpgradeableLoaderState::Uninitialized != program.state()? { + log!(logger, "Program account already initialized"); + return Err(InstructionError::AccountAlreadyInitialized); + } + + if program.lamports()? < rent.minimum_balance(UpgradeableLoaderState::program_len()?) { + log!(logger, "Program account not rent-exempt"); + return Err(InstructionError::ExecutableAccountNotRentExempt); + } + + // Verify Buffer account + + if UpgradeableLoaderState::Buffer != buffer.state()? { + log!(logger, "Invalid Buffer account"); + return Err(InstructionError::InvalidArgument); + } + + let buffer_data_offset = UpgradeableLoaderState::buffer_data_offset()?; + let buffer_data_len = buffer.data_len()?.saturating_sub(buffer_data_offset); + let programdata_data_offset = UpgradeableLoaderState::programdata_data_offset()?; + + if max_data_len < buffer_data_len { + log!(logger, "Max data length is too small to hold Buffer data"); + return Err(InstructionError::AccountDataTooSmall); + } + + // Create ProgramData account + + let (derived_address, bump_seed) = + Pubkey::find_program_address(&[program.unsigned_key().as_ref()], &program_id); + if derived_address != *programdata.unsigned_key() { + log!(logger, "ProgramData address is not derived"); + return Err(InstructionError::InvalidArgument); + } + + MessageProcessor::native_invoke( + invoke_context, + system_instruction::create_account( + payer.unsigned_key(), + programdata.unsigned_key(), + 1.max( + rent.minimum_balance(UpgradeableLoaderState::programdata_len( + max_data_len, + )?), + ), + UpgradeableLoaderState::programdata_len(max_data_len)? as u64, + program_id, + ), + &[payer, programdata, system], + &[&[program.unsigned_key().as_ref(), &[bump_seed]]], + )?; + + // Load and verify the program bits + let _ = create_and_cache_executor( + programdata.unsigned_key(), + &buffer.try_account_ref()?.data[buffer_data_offset..], + invoke_context, + use_jit, + )?; + + // Update the ProgramData account and record the program bits + + programdata.set_state(&UpgradeableLoaderState::ProgramData { + slot: clock.slot, + upgrade_authority_address: authority.cloned(), + })?; + programdata.try_account_ref_mut()?.data + [programdata_data_offset..programdata_data_offset + buffer_data_len] + .copy_from_slice(&buffer.try_account_ref()?.data[buffer_data_offset..]); + + // Update the Program account + + program.set_state(&UpgradeableLoaderState::Program { + programdata_address: *programdata.unsigned_key(), + })?; + program.try_account_ref_mut()?.executable = true; + + // Drain the Buffer account back to the payer + + payer.try_account_ref_mut()?.lamports += buffer.lamports()?; + buffer.try_account_ref_mut()?.lamports = 0; + + log!(logger, "Deployed program {:?}", program.unsigned_key()); + } + UpgradeableLoaderInstruction::Upgrade => { + let programdata = next_keyed_account(account_iter)?; + let program = next_keyed_account(account_iter)?; + let buffer = next_keyed_account(account_iter)?; + let spill = next_keyed_account(account_iter)?; + let rent = from_keyed_account::(next_keyed_account(account_iter)?)?; + let clock = from_keyed_account::(next_keyed_account(account_iter)?)?; + let authority = next_keyed_account(account_iter)?; + + // Verify Program account + + if !program.executable()? { + log!(logger, "Program account not executable"); + return Err(InstructionError::AccountNotExecutable); + } + if &program.owner()? != program_id { + log!(logger, "Program account not owned by loader"); + return Err(InstructionError::IncorrectProgramId); + } + if let UpgradeableLoaderState::Program { + programdata_address, + } = program.state()? + { + if programdata_address != *programdata.unsigned_key() { + log!(logger, "Program and ProgramData account mismatch"); + return Err(InstructionError::InvalidArgument); + } + } else { + log!(logger, "Invalid Program account"); + return Err(InstructionError::InvalidAccountData); + } + + // Verify Buffer account + + if UpgradeableLoaderState::Buffer != buffer.state()? { + log!(logger, "Invalid Buffer account"); + return Err(InstructionError::InvalidAccountData); + } + + let buffer_data_offset = UpgradeableLoaderState::buffer_data_offset()?; + let buffer_data_len = buffer.data_len()?.saturating_sub(buffer_data_offset); + let programdata_data_offset = UpgradeableLoaderState::programdata_data_offset()?; + let programdata_balance_required = 1.max(rent.minimum_balance(programdata.data_len()?)); + + // Verify ProgramData account + + if programdata.data_len()? < UpgradeableLoaderState::programdata_len(buffer_data_len)? { + log!(logger, "ProgramData account not large enough"); + return Err(InstructionError::AccountDataTooSmall); + } + if programdata.lamports()? + buffer.lamports()? < programdata_balance_required { + log!(logger, "Buffer account balance too low to fund upgrade"); + return Err(InstructionError::InsufficientFunds); + } + if let UpgradeableLoaderState::ProgramData { + slot: _, + upgrade_authority_address, + } = programdata.state()? + { + if upgrade_authority_address == None { + log!(logger, "Program not upgradeable"); + return Err(InstructionError::InvalidArgument); + } + if upgrade_authority_address != Some(*authority.unsigned_key()) { + log!(logger, "Upgrade authority not present"); + return Err(InstructionError::MissingRequiredSignature); + } + if authority.signer_key().is_none() { + log!(logger, "Upgrade authority did not sign"); + return Err(InstructionError::MissingRequiredSignature); + } + } else { + log!(logger, "Invalid ProgramData account"); + return Err(InstructionError::InvalidAccountData); + } + + // Load and verify the program bits + + let _ = create_and_cache_executor( + programdata.unsigned_key(), + &buffer.try_account_ref()?.data[buffer_data_offset..], + invoke_context, + use_jit, + )?; + + // Update the ProgramData account and record the upgraded data + + programdata.set_state(&UpgradeableLoaderState::ProgramData { + slot: clock.slot, + upgrade_authority_address: Some(*authority.unsigned_key()), + })?; + programdata.try_account_ref_mut()?.data + [programdata_data_offset..programdata_data_offset + buffer_data_len] + .copy_from_slice(&buffer.try_account_ref()?.data[buffer_data_offset..]); + + // Fund ProgramData to rent-exemption, spill the rest + + spill.try_account_ref_mut()?.lamports += (programdata.lamports()? + + buffer.lamports()?) + .saturating_sub(programdata_balance_required); + buffer.try_account_ref_mut()?.lamports = 0; + programdata.try_account_ref_mut()?.lamports = programdata_balance_required; + + log!(logger, "Upgraded program {:?}", program.unsigned_key()); + } + UpgradeableLoaderInstruction::SetAuthority => { + let programdata = 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); + } + 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 { + slot, + upgrade_authority_address: new_authority.cloned(), + })?; + } else { + log!(logger, "Not a ProgramData account"); + return Err(InstructionError::InvalidAccountData); + } + + log!(logger, "New authority {:?}", new_authority); + } + } + + Ok(()) +} + +fn process_loader_instruction( + program_id: &Pubkey, + keyed_accounts: &[KeyedAccount], + instruction_data: &[u8], + invoke_context: &mut dyn InvokeContext, + use_jit: bool, +) -> Result<(), InstructionError> { + let logger = invoke_context.get_logger(); + let account_iter = &mut keyed_accounts.iter(); + + let program = next_keyed_account(account_iter)?; + if program.owner()? != *program_id { + log!(logger, "Executable account not owned by the BPF loader"); + return Err(InstructionError::IncorrectProgramId); + } + match limited_deserialize(instruction_data)? { + LoaderInstruction::Write { offset, bytes } => { + write_program_data(program, offset as usize, &bytes, invoke_context)?; + } + LoaderInstruction::Finalize => { + if program.signer_key().is_none() { + log!(logger, "key[0] did not sign the transaction"); + return Err(InstructionError::MissingRequiredSignature); + } + + let _ = create_and_cache_executor( + program.unsigned_key(), + &program.try_account_ref()?.data, + invoke_context, + use_jit, + )?; + program.try_account_ref_mut()?.executable = true; + log!(logger, "Finalized account {:?}", program.unsigned_key()); + } + } + + Ok(()) +} + /// Passed to the VM to enforce the compute budget pub struct ThisInstructionMeter { pub compute_meter: Rc>, @@ -273,7 +638,7 @@ impl InstructionMeter for ThisInstructionMeter { /// BPF Loader's Executor implementation pub struct BPFExecutor { - executable: Box>, + program: Box>, } // Well, implement Debug for solana_rbpf::vm::Executable in solana-rbpf... @@ -309,7 +674,7 @@ impl Executor for BPFExecutor { let compute_meter = invoke_context.get_compute_meter(); let mut vm = match create_vm( program_id, - self.executable.as_ref(), + self.program.as_ref(), &mut parameter_bytes, ¶meter_accounts, invoke_context, @@ -374,16 +739,29 @@ impl Executor for BPFExecutor { mod tests { use super::*; use rand::Rng; - use solana_runtime::message_processor::{Executors, ThisInvokeContext}; + use solana_runtime::{ + bank::Bank, + bank_client::BankClient, + message_processor::{Executors, ThisInvokeContext}, + }; use solana_sdk::{ - account::Account, + account::{create_account, Account}, + account_utils::StateMut, + client::SyncClient, + clock::Clock, feature_set::FeatureSet, - instruction::InstructionError, + genesis_config::create_genesis_config, + instruction::Instruction, + instruction::{AccountMeta, InstructionError}, + message::Message, process_instruction::{BpfComputeBudget, MockInvokeContext}, pubkey::Pubkey, rent::Rent, + signature::{Keypair, Signer}, + system_program, sysvar, + transaction::TransactionError, }; - use std::{cell::RefCell, fs::File, io::Read, ops::Range, rc::Rc}; + use std::{cell::RefCell, fs::File, io::Read, ops::Range, rc::Rc, sync::Arc}; struct TestInstructionMeter { remaining: u64, @@ -408,14 +786,14 @@ mod tests { ]; let input = &mut [0x00]; - let executable = Executable::::from_text_bytes( + let program = Executable::::from_text_bytes( program, None, Config::default(), ) .unwrap(); let mut vm = - EbpfVm::::new(executable.as_ref(), input, &[]).unwrap(); + EbpfVm::::new(program.as_ref(), input, &[]).unwrap(); let mut instruction_meter = TestInstructionMeter { remaining: 10 }; vm.execute_program_interpreted(&mut instruction_meter) .unwrap(); @@ -432,7 +810,7 @@ mod tests { #[test] fn test_bpf_loader_write() { - let program_id = solana_sdk::pubkey::new_rand(); + let program_id = bpf_loader::id(); let program_key = solana_sdk::pubkey::new_rand(); let program_account = Account::new_ref(1, 0, &program_id); let keyed_accounts = vec![KeyedAccount::new(&program_key, false, &program_account)]; @@ -499,7 +877,7 @@ mod tests { #[test] fn test_bpf_loader_finalize() { - let program_id = solana_sdk::pubkey::new_rand(); + let program_id = bpf_loader::id(); let program_key = solana_sdk::pubkey::new_rand(); let mut file = File::open("test_elfs/noop_aligned.so").expect("file open failed"); let mut elf = Vec::new(); @@ -563,7 +941,7 @@ mod tests { #[test] fn test_bpf_loader_invoke_main() { - let program_id = solana_sdk::pubkey::new_rand(); + let program_id = bpf_loader::id(); let program_key = solana_sdk::pubkey::new_rand(); // Create program account @@ -590,7 +968,7 @@ mod tests { process_instruction(&bpf_loader::id(), &keyed_accounts, &[], &mut invoke_context) ); - // Case: Account not executable + // Case: Account not program keyed_accounts[0].account.borrow_mut().executable = false; assert_eq!( Err(InstructionError::InvalidInstructionData), @@ -655,7 +1033,7 @@ mod tests { #[test] fn test_bpf_loader_serialize_unaligned() { - let program_id = solana_sdk::pubkey::new_rand(); + let program_id = bpf_loader_deprecated::id(); let program_key = solana_sdk::pubkey::new_rand(); // Create program account @@ -699,7 +1077,7 @@ mod tests { #[test] fn test_bpf_loader_serialize_aligned() { - let program_id = solana_sdk::pubkey::new_rand(); + let program_id = bpf_loader::id(); let program_key = solana_sdk::pubkey::new_rand(); // Create program account @@ -741,6 +1119,1274 @@ mod tests { ); } + #[test] + fn test_bpf_loader_upgradeable_initialize_buffer() { + let instruction = + bincode::serialize(&UpgradeableLoaderInstruction::InitializeBuffer).unwrap(); + let buffer_address = Pubkey::new_unique(); + let buffer_account = Account::new_ref( + 1, + UpgradeableLoaderState::buffer_len(9).unwrap(), + &bpf_loader_upgradeable::id(), + ); + + // Case: Success + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[KeyedAccount::new(&buffer_address, false, &buffer_account),], + &instruction, + &mut MockInvokeContext::default() + ) + ); + let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); + assert_eq!(state, UpgradeableLoaderState::Buffer); + + // Case: Already initialized + assert_eq!( + Err(InstructionError::AccountAlreadyInitialized), + process_instruction( + &bpf_loader_upgradeable::id(), + &[KeyedAccount::new(&buffer_address, false, &buffer_account),], + &instruction, + &mut MockInvokeContext::default() + ) + ); + let state: UpgradeableLoaderState = buffer_account.borrow().state().unwrap(); + assert_eq!(state, UpgradeableLoaderState::Buffer); + } + + #[test] + fn test_bpf_loader_upgradeable_write() { + let buffer_address = Pubkey::new_unique(); + let buffer_account = Account::new_ref( + 1, + UpgradeableLoaderState::buffer_len(9).unwrap(), + &bpf_loader_upgradeable::id(), + ); + + // Case: Not initialized + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { + offset: 0, + bytes: vec![42; 9], + }) + .unwrap(); + assert_eq!( + Err(InstructionError::InvalidAccountData), + process_instruction( + &bpf_loader_upgradeable::id(), + &[KeyedAccount::new(&buffer_address, true, &buffer_account),], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: Write entire buffer + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { + offset: 0, + bytes: vec![42; 9], + }) + .unwrap(); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer) + .unwrap(); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[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!( + &buffer_account.borrow().data[UpgradeableLoaderState::buffer_data_offset().unwrap()..], + &[42; 9] + ); + + // Case: Write portion of the buffer + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { + offset: 3, + bytes: vec![42; 6], + }) + .unwrap(); + let buffer_account = Account::new_ref( + 1, + UpgradeableLoaderState::buffer_len(9).unwrap(), + &bpf_loader_upgradeable::id(), + ); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer) + .unwrap(); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[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!( + &buffer_account.borrow().data[UpgradeableLoaderState::buffer_data_offset().unwrap()..], + &[0, 0, 0, 42, 42, 42, 42, 42, 42] + ); + + // Case: Not signed + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { + offset: 0, + bytes: vec![42; 9], + }) + .unwrap(); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer) + .unwrap(); + assert_eq!( + Err(InstructionError::MissingRequiredSignature), + process_instruction( + &bpf_loader_upgradeable::id(), + &[KeyedAccount::new(&buffer_address, false, &buffer_account),], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: overflow size + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { + offset: 0, + bytes: vec![42; 10], + }) + .unwrap(); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer) + .unwrap(); + assert_eq!( + Err(InstructionError::AccountDataTooSmall), + process_instruction( + &bpf_loader_upgradeable::id(), + &[KeyedAccount::new(&buffer_address, true, &buffer_account),], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: overflow offset + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Write { + offset: 1, + bytes: vec![42; 9], + }) + .unwrap(); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer) + .unwrap(); + assert_eq!( + Err(InstructionError::AccountDataTooSmall), + process_instruction( + &bpf_loader_upgradeable::id(), + &[KeyedAccount::new(&buffer_address, true, &buffer_account),], + &instruction, + &mut MockInvokeContext::default() + ) + ); + } + + #[test] + fn test_bpf_loader_upgradeable_deploy_with_max_len() { + let (genesis_config, mint_keypair) = create_genesis_config(1_000_000_000); + let mut bank = Bank::new(&genesis_config); + bank.add_builtin( + "solana_bpf_loader_upgradeable_program", + bpf_loader_upgradeable::id(), + process_instruction, + ); + let bank = Arc::new(bank); + let bank_client = BankClient::new_shared(&bank); + + // Setup initial accounts + let program_keypair = Keypair::new(); + let (programdata_address, _) = Pubkey::find_program_address( + &[program_keypair.pubkey().as_ref()], + &bpf_loader_upgradeable::id(), + ); + let upgrade_authority_address = Pubkey::new_unique(); + let mut file = File::open("test_elfs/noop_aligned.so").expect("file open failed"); + let mut elf = Vec::new(); + file.read_to_end(&mut elf).unwrap(); + let min_program_balance = bank + .get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::program_len().unwrap()); + let min_programdata_balance = bank.get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::programdata_len(elf.len()).unwrap(), + ); + let buffer_address = Pubkey::new_unique(); + let mut buffer_account = Account::new( + min_programdata_balance, + UpgradeableLoaderState::buffer_len(elf.len()).unwrap(), + &bpf_loader_upgradeable::id(), + ); + buffer_account + .set_state(&UpgradeableLoaderState::Buffer) + .unwrap(); + buffer_account.data[UpgradeableLoaderState::buffer_data_offset().unwrap()..] + .copy_from_slice(&elf); + + // Test successful deploy + bank.clear_signatures(); + bank.store_account(&buffer_address, &buffer_account); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + bank.store_account(&programdata_address, &Account::default()); + let before = bank.get_balance(&mint_keypair.pubkey()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + Some(&upgrade_authority_address), + min_program_balance, + elf.len(), + ) + .unwrap(), + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .is_ok()); + assert_eq!( + bank.get_balance(&mint_keypair.pubkey()), + before - min_program_balance + ); + assert_eq!(bank.get_balance(&buffer_address), 0); + assert_eq!(None, bank.get_account(&buffer_address)); + let post_program_account = bank.get_account(&program_keypair.pubkey()).unwrap(); + assert_eq!(post_program_account.lamports, min_program_balance); + assert_eq!(post_program_account.owner, bpf_loader_upgradeable::id()); + assert_eq!( + post_program_account.data.len(), + UpgradeableLoaderState::program_len().unwrap() + ); + let state: UpgradeableLoaderState = post_program_account.state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::Program { + programdata_address + } + ); + let post_programdata_account = bank.get_account(&programdata_address).unwrap(); + assert_eq!(post_programdata_account.lamports, min_programdata_balance); + assert_eq!(post_programdata_account.owner, bpf_loader_upgradeable::id()); + let state: UpgradeableLoaderState = post_programdata_account.state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::ProgramData { + slot: bank_client.get_slot().unwrap(), + upgrade_authority_address: Some(upgrade_authority_address) + } + ); + for (i, byte) in post_programdata_account.data + [UpgradeableLoaderState::programdata_data_offset().unwrap()..] + .iter() + .enumerate() + { + assert_eq!(elf[i], *byte); + } + + // Test initialized program account + bank.clear_signatures(); + bank.store_account(&buffer_address, &buffer_account); + let message = Message::new( + &[Instruction::new( + bpf_loader_upgradeable::id(), + &UpgradeableLoaderInstruction::DeployWithMaxDataLen { + max_data_len: elf.len(), + }, + vec![ + AccountMeta::new(mint_keypair.pubkey(), true), + AccountMeta::new(programdata_address, false), + AccountMeta::new(program_keypair.pubkey(), false), + AccountMeta::new(buffer_address, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(system_program::id(), false), + ], + )], + Some(&mint_keypair.pubkey()), + ); + assert_eq!( + TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized), + bank_client + .send_and_confirm_message(&[&mint_keypair], message) + .unwrap_err() + .unwrap() + ); + + // Test successful deploy no authority + bank.clear_signatures(); + bank.store_account(&buffer_address, &buffer_account); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + bank.store_account(&programdata_address, &Account::default()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + None, + min_program_balance, + elf.len(), + ) + .unwrap(), + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .is_ok()); + assert_eq!(None, bank.get_account(&buffer_address)); + let post_program_account = bank.get_account(&program_keypair.pubkey()).unwrap(); + assert_eq!(post_program_account.lamports, min_program_balance); + assert_eq!(post_program_account.owner, bpf_loader_upgradeable::id()); + assert_eq!( + post_program_account.data.len(), + UpgradeableLoaderState::program_len().unwrap() + ); + let state: UpgradeableLoaderState = post_program_account.state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::Program { + programdata_address + } + ); + let post_programdata_account = bank.get_account(&programdata_address).unwrap(); + assert_eq!(post_programdata_account.lamports, min_programdata_balance); + assert_eq!(post_programdata_account.owner, bpf_loader_upgradeable::id()); + let state: UpgradeableLoaderState = post_programdata_account.state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::ProgramData { + slot: bank_client.get_slot().unwrap(), + upgrade_authority_address: None + } + ); + + // Test initialized ProgramData account + bank.clear_signatures(); + bank.store_account(&buffer_address, &buffer_account); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + None, + min_program_balance, + elf.len(), + ) + .unwrap(), + Some(&mint_keypair.pubkey()), + ); + assert_eq!( + TransactionError::InstructionError(1, InstructionError::Custom(0)), + bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .unwrap_err() + .unwrap() + ); + + // Test invalid Buffer account state + bank.clear_signatures(); + bank.store_account(&buffer_address, &Account::default()); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + bank.store_account(&programdata_address, &Account::default()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + None, + min_program_balance, + elf.len(), + ) + .unwrap(), + Some(&mint_keypair.pubkey()), + ); + assert_eq!( + TransactionError::InstructionError(1, InstructionError::InvalidAccountData), + bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .unwrap_err() + .unwrap() + ); + + // Test program account not rent exempt + bank.clear_signatures(); + bank.store_account(&buffer_address, &buffer_account); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + bank.store_account(&programdata_address, &Account::default()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + None, + min_program_balance - 1, + elf.len(), + ) + .unwrap(), + Some(&mint_keypair.pubkey()), + ); + assert_eq!( + TransactionError::InstructionError(1, InstructionError::ExecutableAccountNotRentExempt), + bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .unwrap_err() + .unwrap() + ); + + // Test Insufficient payer funds + bank.clear_signatures(); + bank.store_account( + &mint_keypair.pubkey(), + &Account::new(min_program_balance, 0, &system_program::id()), + ); + bank.store_account(&buffer_address, &buffer_account); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + bank.store_account(&programdata_address, &Account::default()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + None, + min_program_balance, + elf.len(), + ) + .unwrap(), + Some(&mint_keypair.pubkey()), + ); + assert_eq!( + TransactionError::InstructionError(1, InstructionError::Custom(1)), + bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .unwrap_err() + .unwrap() + ); + bank.store_account( + &mint_keypair.pubkey(), + &Account::new(1_000_000_000, 0, &system_program::id()), + ); + + // Test max_data_len + bank.clear_signatures(); + bank.store_account(&buffer_address, &buffer_account); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + bank.store_account(&programdata_address, &Account::default()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + None, + min_program_balance, + elf.len() - 1, + ) + .unwrap(), + Some(&mint_keypair.pubkey()), + ); + assert_eq!( + TransactionError::InstructionError(1, InstructionError::AccountDataTooSmall), + bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .unwrap_err() + .unwrap() + ); + + // Test not the system account + bank.clear_signatures(); + bank.store_account(&buffer_address, &buffer_account); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + bank.store_account(&programdata_address, &Account::default()); + let mut instructions = bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + None, + min_program_balance, + elf.len(), + ) + .unwrap(); + instructions[1].accounts[6] = AccountMeta::new_readonly(Pubkey::new_unique(), false); + let message = Message::new(&instructions, Some(&mint_keypair.pubkey())); + assert_eq!( + TransactionError::InstructionError(1, InstructionError::MissingAccount), + bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .unwrap_err() + .unwrap() + ); + + // Test Bad ELF data + bank.clear_signatures(); + let mut modified_buffer_account = buffer_account; + modified_buffer_account + .data + .truncate(UpgradeableLoaderState::buffer_len(1).unwrap()); + bank.store_account(&buffer_address, &modified_buffer_account); + bank.store_account(&program_keypair.pubkey(), &Account::default()); + bank.store_account(&programdata_address, &Account::default()); + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &mint_keypair.pubkey(), + &program_keypair.pubkey(), + &buffer_address, + None, + min_program_balance, + elf.len(), + ) + .unwrap(), + Some(&mint_keypair.pubkey()), + ); + assert_eq!( + TransactionError::InstructionError(1, InstructionError::InvalidAccountData), + bank_client + .send_and_confirm_message(&[&mint_keypair, &program_keypair], message) + .unwrap_err() + .unwrap() + ); + } + + #[test] + fn test_bpf_loader_upgradeable_upgrade() { + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::Upgrade).unwrap(); + let mut file = File::open("test_elfs/noop_aligned.so").expect("file open failed"); + let mut elf_orig = Vec::new(); + file.read_to_end(&mut elf_orig).unwrap(); + let mut file = File::open("test_elfs/noop_unaligned.so").expect("file open failed"); + let mut elf_new = Vec::new(); + file.read_to_end(&mut elf_new).unwrap(); + assert_ne!(elf_orig.len(), elf_new.len()); + let rent = Rent::default(); + let rent_account = RefCell::new(create_account(&Rent::default(), 1)); + let slot = 42; + let clock_account = RefCell::new(create_account( + &Clock { + slot, + ..Clock::default() + }, + 1, + )); + let min_program_balance = + 1.max(rent.minimum_balance(UpgradeableLoaderState::program_len().unwrap())); + let min_programdata_balance = 1.max(rent.minimum_balance( + UpgradeableLoaderState::programdata_len(elf_orig.len().max(elf_new.len())).unwrap(), + )); + let upgrade_authority_address = Pubkey::new_unique(); + let buffer_address = Pubkey::new_unique(); + let program_address = Pubkey::new_unique(); + let (programdata_address, _) = + Pubkey::find_program_address(&[program_address.as_ref()], &id()); + let spill_address = Pubkey::new_unique(); + let upgrade_authority_account = Account::new_ref(1, 0, &Pubkey::new_unique()); + + #[allow(clippy::type_complexity)] + fn get_accounts( + programdata_address: &Pubkey, + upgrade_authority_address: &Pubkey, + slot: u64, + elf_orig: &[u8], + elf_new: &[u8], + min_program_balance: u64, + min_programdata_balance: u64, + ) -> ( + Rc>, + Rc>, + Rc>, + Rc>, + ) { + let buffer_account = Account::new_ref( + 1, + UpgradeableLoaderState::buffer_len(elf_new.len()).unwrap(), + &bpf_loader_upgradeable::id(), + ); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer) + .unwrap(); + buffer_account.borrow_mut().data + [UpgradeableLoaderState::buffer_data_offset().unwrap()..] + .copy_from_slice(&elf_new); + let programdata_account = Account::new_ref( + min_programdata_balance, + UpgradeableLoaderState::programdata_len(elf_orig.len().max(elf_new.len())).unwrap(), + &bpf_loader_upgradeable::id(), + ); + programdata_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(*upgrade_authority_address), + }) + .unwrap(); + let program_account = Account::new_ref( + min_program_balance, + UpgradeableLoaderState::program_len().unwrap(), + &bpf_loader_upgradeable::id(), + ); + program_account.borrow_mut().executable = true; + program_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Program { + programdata_address: *programdata_address, + }) + .unwrap(); + let spill_account = Account::new_ref(0, 0, &Pubkey::new_unique()); + + ( + buffer_account, + program_account, + programdata_account, + spill_account, + ) + } + + // Case: Success + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + assert_eq!(0, buffer_account.borrow().lamports); + assert_eq!( + min_programdata_balance, + programdata_account.borrow().lamports + ); + assert_eq!(1, spill_account.borrow().lamports); + let state: UpgradeableLoaderState = programdata_account.borrow().state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(upgrade_authority_address) + } + ); + for (i, byte) in programdata_account.borrow().data + [UpgradeableLoaderState::programdata_data_offset().unwrap() + ..UpgradeableLoaderState::programdata_data_offset().unwrap() + elf_new.len()] + .iter() + .enumerate() + { + assert_eq!(elf_new[i], *byte); + } + + // Case: not upgradable + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + programdata_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: None, + }) + .unwrap(); + assert_eq!( + Err(InstructionError::InvalidArgument), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: wrong authority + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + assert_eq!( + Err(InstructionError::MissingRequiredSignature), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &Pubkey::new_unique(), + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: authority did not sign + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + assert_eq!( + Err(InstructionError::MissingRequiredSignature), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + false, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: Program account not executable + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + program_account.borrow_mut().executable = false; + assert_eq!( + Err(InstructionError::AccountNotExecutable), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: Program account now owned by loader + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + program_account.borrow_mut().owner = Pubkey::new_unique(); + assert_eq!( + Err(InstructionError::IncorrectProgramId), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: Program account not initialized + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + program_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Uninitialized) + .unwrap(); + assert_eq!( + Err(InstructionError::InvalidAccountData), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: ProgramData account not initialized + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + programdata_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Uninitialized) + .unwrap(); + assert_eq!( + Err(InstructionError::InvalidAccountData), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: Program ProgramData account mismatch + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + assert_eq!( + Err(InstructionError::InvalidArgument), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&Pubkey::new_unique(), false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: Buffer account not initialized + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Uninitialized) + .unwrap(); + assert_eq!( + Err(InstructionError::InvalidAccountData), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: Buffer account too big + let (_, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + let buffer_account = Account::new_ref( + 1, + UpgradeableLoaderState::buffer_len(elf_orig.len().max(elf_new.len()) + 1).unwrap(), + &bpf_loader_upgradeable::id(), + ); + buffer_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::Buffer) + .unwrap(); + assert_eq!( + Err(InstructionError::AccountDataTooSmall), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: bad elf data + let (buffer_account, program_account, programdata_account, spill_account) = get_accounts( + &programdata_address, + &upgrade_authority_address, + slot, + &elf_orig, + &elf_new, + min_program_balance, + min_programdata_balance, + ); + buffer_account.borrow_mut().data[UpgradeableLoaderState::buffer_data_offset().unwrap()..] + .copy_from_slice(&vec![0; elf_new.len()]); + assert_eq!( + Err(InstructionError::InvalidAccountData), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly(&program_address, false, &program_account), + KeyedAccount::new(&buffer_address, false, &buffer_account), + KeyedAccount::new(&spill_address, false, &spill_account), + KeyedAccount::new_readonly(&sysvar::rent::id(), false, &rent_account), + KeyedAccount::new_readonly(&sysvar::clock::id(), false, &clock_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + } + + #[test] + fn test_bpf_loader_upgradeable_set_upgrade_authority() { + let instruction = bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(); + let slot = 0; + let upgrade_authority_address = Pubkey::new_unique(); + let upgrade_authority_account = Account::new_ref(1, 0, &Pubkey::new_unique()); + let new_upgrade_authority_address = Pubkey::new_unique(); + let new_upgrade_authority_account = Account::new_ref(1, 0, &Pubkey::new_unique()); + let program_address = Pubkey::new_unique(); + let (programdata_address, _) = + Pubkey::find_program_address(&[program_address.as_ref()], &id()); + let programdata_account = Account::new_ref( + 1, + UpgradeableLoaderState::programdata_len(0).unwrap(), + &bpf_loader_upgradeable::id(), + ); + + // Case: Set to new authority + programdata_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(upgrade_authority_address), + }) + .unwrap(); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ), + KeyedAccount::new_readonly( + &new_upgrade_authority_address, + false, + &new_upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + let state: UpgradeableLoaderState = programdata_account.borrow().state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(new_upgrade_authority_address), + } + ); + + // Case: Not upgradeable + programdata_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(upgrade_authority_address), + }) + .unwrap(); + assert_eq!( + Ok(()), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + true, + &upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + let state: UpgradeableLoaderState = programdata_account.borrow().state().unwrap(); + assert_eq!( + state, + UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: None, + } + ); + + // Case: Authority did not sign + programdata_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(upgrade_authority_address), + }) + .unwrap(); + assert_eq!( + Err(InstructionError::MissingRequiredSignature), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly( + &upgrade_authority_address, + false, + &upgrade_authority_account + ), + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: wrong authority + programdata_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: Some(upgrade_authority_address), + }) + .unwrap(); + assert_eq!( + Err(InstructionError::MissingRequiredSignature), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly( + &Pubkey::new_unique(), + true, + &upgrade_authority_account + ), + KeyedAccount::new_readonly( + &new_upgrade_authority_address, + false, + &new_upgrade_authority_account + ) + ], + &instruction, + &mut MockInvokeContext::default() + ) + ); + + // Case: No authority + programdata_account + .borrow_mut() + .set_state(&UpgradeableLoaderState::ProgramData { + slot, + upgrade_authority_address: None, + }) + .unwrap(); + assert_eq!( + Err(InstructionError::InvalidArgument), + process_instruction( + &bpf_loader_upgradeable::id(), + &[ + KeyedAccount::new(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly( + &Pubkey::new_unique(), + true, + &upgrade_authority_account + ), + ], + &bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(), + &mut MockInvokeContext::default() + ) + ); + + // Case: Not a ProgramData account + programdata_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(&programdata_address, false, &programdata_account), + KeyedAccount::new_readonly( + &Pubkey::new_unique(), + true, + &upgrade_authority_account + ), + ], + &bincode::serialize(&UpgradeableLoaderInstruction::SetAuthority).unwrap(), + &mut MockInvokeContext::default() + ) + ); + } + /// fuzzing utility function fn fuzz( bytes: &[u8], diff --git a/programs/bpf_loader/src/upgradeable.rs b/programs/bpf_loader/src/upgradeable.rs new file mode 100644 index 0000000000..89aaed9fb5 --- /dev/null +++ b/programs/bpf_loader/src/upgradeable.rs @@ -0,0 +1,6 @@ +solana_sdk::declare_builtin!( + solana_sdk::bpf_loader_upgradeable::ID, + solana_bpf_loader_upgradeable_program, + solana_bpf_loader_program::process_instruction, + upgradeable::id +); diff --git a/programs/bpf_loader/src/upgradeable_with_jit.rs b/programs/bpf_loader/src/upgradeable_with_jit.rs new file mode 100644 index 0000000000..da479a400a --- /dev/null +++ b/programs/bpf_loader/src/upgradeable_with_jit.rs @@ -0,0 +1,6 @@ +solana_sdk::declare_builtin!( + solana_sdk::bpf_loader_upgradeable::ID, + solana_bpf_loader_upgradeable_program_with_jit, + solana_bpf_loader_program::process_instruction_jit, + upgradeable_with_jit::id +); diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index e7df609fd6..76cd4ba482 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -16,6 +16,7 @@ use rayon::slice::ParallelSliceMut; use solana_sdk::{ account::Account, account_utils::StateMut, + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::{Epoch, Slot}, feature_set::{self, FeatureSet}, fee_calculator::{FeeCalculator, FeeConfig}, @@ -236,7 +237,7 @@ impl Accounts { let mut program_id = *program_id; loop { if native_loader::check_id(&program_id) { - // at the root of the chain, ready to dispatch + // At the root of the chain, ready to dispatch break; } @@ -262,8 +263,31 @@ impl Accounts { return Err(TransactionError::InvalidProgramForExecution); } - // add loader to chain + // Add loader to chain let program_owner = program.owner; + + if bpf_loader_upgradeable::check_id(&program_owner) { + // The upgradeable loader requires the derived ProgramData account + if let Ok(UpgradeableLoaderState::Program { + programdata_address, + }) = program.state() + { + if let Some(program) = self + .accounts_db + .load(ancestors, &programdata_address) + .map(|(account, _)| account) + { + accounts.insert(0, (programdata_address, program)); + } else { + error_counters.account_not_found += 1; + return Err(TransactionError::ProgramAccountNotFound); + } + } else { + error_counters.invalid_program_for_execution += 1; + return Err(TransactionError::InvalidProgramForExecution); + } + } + accounts.insert(0, (program_id, program)); program_id = program_owner; } diff --git a/runtime/src/loader_utils.rs b/runtime/src/loader_utils.rs index 7cdc5b8d28..a2228da5e3 100644 --- a/runtime/src/loader_utils.rs +++ b/runtime/src/loader_utils.rs @@ -1,5 +1,6 @@ use serde::Serialize; use solana_sdk::{ + bpf_loader_upgradeable::{self, UpgradeableLoaderState}, client::Client, instruction::{AccountMeta, Instruction}, loader_instruction, @@ -53,6 +54,132 @@ pub fn load_program( program_pubkey } +pub fn load_buffer_account( + bank_client: &T, + from_keypair: &Keypair, + program: &[u8], +) -> Pubkey { + let buffer_keypair = Keypair::new(); + let buffer_pubkey = buffer_keypair.pubkey(); + + bank_client + .send_and_confirm_message( + &[from_keypair, &buffer_keypair], + Message::new( + &bpf_loader_upgradeable::create_buffer( + &from_keypair.pubkey(), + &buffer_pubkey, + 1.max( + bank_client + .get_minimum_balance_for_rent_exemption(program.len()) + .unwrap(), + ), + program.len(), + ) + .unwrap(), + Some(&from_keypair.pubkey()), + ), + ) + .unwrap(); + + let chunk_size = 256; // Size of chunk just needs to fit into tx + let mut offset = 0; + for chunk in program.chunks(chunk_size) { + let message = Message::new( + &[bpf_loader_upgradeable::write( + &buffer_pubkey, + offset, + chunk.to_vec(), + )], + Some(&from_keypair.pubkey()), + ); + bank_client + .send_and_confirm_message(&[from_keypair, &buffer_keypair], message) + .unwrap(); + offset += chunk_size as u32; + } + buffer_keypair.pubkey() +} + +pub fn load_upgradeable_program( + bank_client: &T, + from_keypair: &Keypair, + program: Vec, +) -> (Pubkey, Keypair) { + let executable_keypair = Keypair::new(); + let program_pubkey = executable_keypair.pubkey(); + let authority_keypair = Keypair::new(); + let authority_pubkey = authority_keypair.pubkey(); + + let buffer_pubkey = load_buffer_account(bank_client, &from_keypair, &program); + + let message = Message::new( + &bpf_loader_upgradeable::deploy_with_max_program_len( + &from_keypair.pubkey(), + &program_pubkey, + &buffer_pubkey, + Some(&authority_pubkey), + 1.max( + bank_client + .get_minimum_balance_for_rent_exemption( + UpgradeableLoaderState::program_len().unwrap(), + ) + .unwrap(), + ), + program.len() * 2, + ) + .unwrap(), + Some(&from_keypair.pubkey()), + ); + bank_client + .send_and_confirm_message(&[from_keypair, &executable_keypair], message) + .unwrap(); + + (executable_keypair.pubkey(), authority_keypair) +} + +pub fn upgrade_program( + bank_client: &T, + from_keypair: &Keypair, + program_pubkey: &Pubkey, + buffer_pubkey: &Pubkey, + authority_keypair: &Keypair, + spill_pubkey: &Pubkey, +) { + let message = Message::new( + &[bpf_loader_upgradeable::upgrade( + &program_pubkey, + &buffer_pubkey, + &authority_keypair.pubkey(), + &spill_pubkey, + )], + Some(&from_keypair.pubkey()), + ); + bank_client + .send_and_confirm_message(&[from_keypair, &authority_keypair], message) + .unwrap(); +} + +pub fn set_upgrade_authority( + bank_client: &T, + from_keypair: &Keypair, + program_pubkey: &Pubkey, + current_authority_keypair: &Keypair, + new_authority_pubkey: Option<&Pubkey>, +) { + let message = Message::new( + &[bpf_loader_upgradeable::set_authority( + program_pubkey, + ¤t_authority_keypair.pubkey(), + new_authority_pubkey, + )], + Some(&from_keypair.pubkey()), + ); + bank_client + .send_and_confirm_message(&[from_keypair, ¤t_authority_keypair], message) + .unwrap(); +} + // Return an Instruction that invokes `program_id` with `data` and required // a signature from `from_pubkey`. pub fn create_invoke_instruction( diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 9c35df6ec5..ea9eb7a1fe 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -590,6 +590,74 @@ impl MessageProcessor { Ok((message, id, index)) } + /// Entrypoint for a cross-program invocation from a native program + pub fn native_invoke( + invoke_context: &mut dyn InvokeContext, + instruction: Instruction, + keyed_accounts: &[&KeyedAccount], + signers_seeds: &[&[&[u8]]], + ) -> Result<(), InstructionError> { + let caller_program_id = invoke_context.get_caller()?; + + // Translate and verify caller's data + + let signers = signers_seeds + .iter() + .map(|seeds| Pubkey::create_program_address(&seeds, caller_program_id)) + .collect::, solana_sdk::pubkey::PubkeyError>>()?; + let (message, callee_program_id, callee_program_id_index) = + Self::create_message(&instruction, &keyed_accounts, &signers)?; + let mut accounts = vec![]; + let mut account_refs = vec![]; + 'root: for account_key in message.account_keys.iter() { + for keyed_account in keyed_accounts { + if account_key == keyed_account.unsigned_key() { + accounts.push(Rc::new(keyed_account.account.clone())); + account_refs.push(keyed_account); + continue 'root; + } + } + return Err(InstructionError::MissingAccount); + } + + // Process instruction + + invoke_context.record_instruction(&instruction); + let program_account = (**accounts + .get(callee_program_id_index) + .ok_or(InstructionError::MissingAccount)?) + .clone(); + if !program_account.borrow().executable { + return Err(InstructionError::AccountNotExecutable); + } + let executable_accounts = vec![(callee_program_id, program_account)]; + + MessageProcessor::process_cross_program_instruction( + &message, + &executable_accounts, + &accounts, + invoke_context, + )?; + + // Copy results back to caller + + for (i, (account, account_ref)) in accounts.iter().zip(account_refs).enumerate() { + let account = account.borrow(); + if message.is_writable(i) && !account.executable { + account_ref.try_account_ref_mut()?.lamports = account.lamports; + account_ref.try_account_ref_mut()?.owner = account.owner; + if account_ref.data_len()? != account.data.len() && account_ref.data_len()? != 0 { + // Only support for `CreateAccount` at this time. + // Need a way to limit total realloc size across multiple CPI calls + return Err(InstructionError::InvalidRealloc); + } + account_ref.try_account_ref_mut()?.data = account.data.clone(); + } + } + + Ok(()) + } + /// Process a cross-program instruction /// This method calls the instruction's program entrypoint function pub fn process_cross_program_instruction( diff --git a/sdk/program/src/bpf_loader_upgradeable.rs b/sdk/program/src/bpf_loader_upgradeable.rs new file mode 100644 index 0000000000..766aa95fb9 --- /dev/null +++ b/sdk/program/src/bpf_loader_upgradeable.rs @@ -0,0 +1,213 @@ +//! @brief An Upgradeable Solana BPF loader. +//! +//! The upgradeable BPF loader is responsible for deploying, upgrading, and +//! executing BPF programs. The upgradeable loader allows a program's authority +//! to update the program at any time. This ability break's the "code is law" +//! contract the usually enforces the policy that once a program is on-chain it +//! becomes immutable. Because of this, care should be taken before executing +//! upgradeable programs which still have a functioning authority. For more +//! information refer to `loader_upgradeable_instruction.rs` + +use crate::{ + instruction::{AccountMeta, Instruction, InstructionError}, + loader_upgradeable_instruction::UpgradeableLoaderInstruction, + pubkey::Pubkey, + system_instruction, sysvar, +}; +use bincode::serialized_size; + +crate::declare_id!("BPFLoaderUpgradeab1e11111111111111111111111"); + +/// Upgradeable loader account states +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)] +pub enum UpgradeableLoaderState { + /// Account is not initialized. + Uninitialized, + /// A Buffer account. + Buffer, + /// An Program account. + Program { + /// Address of the ProgramData account. + programdata_address: Pubkey, + }, + // A ProgramData account. + ProgramData { + /// Slot that the program was last modified. + slot: u64, + /// Address of the Program's upgrade authority. + upgrade_authority_address: Option, + // The raw program data follows this serialized structure in the + // account's data. + }, +} +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)? + + program_len) + } + /// Offset into the ProgramData account's data of the program bits. + pub fn buffer_data_offset() -> Result { + Self::buffer_len(0) + } + /// Length of an executable account's data. + pub fn program_len() -> Result { + serialized_size(&Self::Program { + programdata_address: Pubkey::default(), + }) + .map(|len| len as usize) + .map_err(|_| InstructionError::InvalidInstructionData) + } + /// Length of a ProgramData account's data. + pub fn programdata_len(program_len: usize) -> Result { + Ok(serialized_size(&Self::ProgramData { + slot: 0, + upgrade_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. + pub fn programdata_data_offset() -> Result { + Self::programdata_len(0) + } +} + +/// Returns the instructions required to initialize a Buffer account. +pub fn create_buffer( + payer_address: &Pubkey, + buffer_address: &Pubkey, + lamports: u64, + program_len: usize, +) -> Result, InstructionError> { + Ok(vec![ + system_instruction::create_account( + payer_address, + buffer_address, + lamports, + UpgradeableLoaderState::buffer_len(program_len)? as u64, + &id(), + ), + Instruction::new( + id(), + &UpgradeableLoaderInstruction::InitializeBuffer, + vec![AccountMeta::new(*buffer_address, false)], + ), + ]) +} + +/// 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 { + Instruction::new( + id(), + &UpgradeableLoaderInstruction::Write { offset, bytes }, + vec![AccountMeta::new(*buffer_address, true)], + ) +} + +/// Returns the instructions required to deploy a program with a specified +/// maximum program length. The maximum length must be large enough to +/// accommodate any future upgrades. +pub fn deploy_with_max_program_len( + payer_address: &Pubkey, + program_address: &Pubkey, + buffer_address: &Pubkey, + upgrade_authority_address: Option<&Pubkey>, + program_lamports: u64, + max_data_len: usize, +) -> Result, InstructionError> { + let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); + let mut metas = vec![ + AccountMeta::new(*payer_address, true), + AccountMeta::new(programdata_address, false), + AccountMeta::new(*program_address, false), + AccountMeta::new(*buffer_address, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(crate::system_program::id(), false), + ]; + if let Some(address) = upgrade_authority_address { + metas.push(AccountMeta::new_readonly(*address, false)); + } + Ok(vec![ + system_instruction::create_account( + payer_address, + program_address, + program_lamports, + UpgradeableLoaderState::program_len()? as u64, + &id(), + ), + Instruction::new( + id(), + &UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len }, + metas, + ), + ]) +} + +/// Returns the instructions required to upgrade a program. +pub fn upgrade( + program_address: &Pubkey, + buffer_address: &Pubkey, + authority_address: &Pubkey, + spill_address: &Pubkey, +) -> Instruction { + let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); + Instruction::new( + id(), + &UpgradeableLoaderInstruction::Upgrade, + vec![ + AccountMeta::new(programdata_address, false), + AccountMeta::new_readonly(*program_address, false), + AccountMeta::new(*buffer_address, false), + AccountMeta::new(*spill_address, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + AccountMeta::new_readonly(sysvar::clock::id(), false), + AccountMeta::new_readonly(*authority_address, true), + ], + ) +} + +/// Returns the instructions required to set a program's authority. +pub fn set_authority( + program_address: &Pubkey, + current_authority_address: &Pubkey, + new_authority_address: Option<&Pubkey>, +) -> Instruction { + let (programdata_address, _) = Pubkey::find_program_address(&[program_address.as_ref()], &id()); + + let mut metas = vec![ + AccountMeta::new(programdata_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) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_account_lengths() { + assert_eq!( + 4, + serialized_size(&UpgradeableLoaderState::Uninitialized).unwrap() + ); + assert_eq!(36, UpgradeableLoaderState::program_len().unwrap()); + assert_eq!( + 45, + UpgradeableLoaderState::programdata_data_offset().unwrap() + ); + assert_eq!( + 45 + 42, + UpgradeableLoaderState::programdata_len(42).unwrap() + ); + } +} diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index de8a79cce1..a99075f98d 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -7,6 +7,7 @@ extern crate self as solana_program; pub mod account_info; pub mod bpf_loader; pub mod bpf_loader_deprecated; +pub mod bpf_loader_upgradeable; pub mod clock; pub mod decode_error; pub mod entrypoint; @@ -18,6 +19,7 @@ pub mod hash; pub mod incinerator; pub mod instruction; pub mod loader_instruction; +pub mod loader_upgradeable_instruction; pub mod log; pub mod message; pub mod native_token; diff --git a/sdk/program/src/loader_upgradeable_instruction.rs b/sdk/program/src/loader_upgradeable_instruction.rs new file mode 100644 index 0000000000..35a8d277c8 --- /dev/null +++ b/sdk/program/src/loader_upgradeable_instruction.rs @@ -0,0 +1,104 @@ +//! Upgradeable loader instruction definitions + +#[repr(u8)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub enum UpgradeableLoaderInstruction { + /// Initialize a Buffer account. + /// + /// A Buffer account is an intermediary that once fully populated is used + /// with the `DeployWithMaxDataLen` instruction to populate the program's + /// ProgramData account. + /// + /// The `InitializeBuffer` instruction requires no signers and MUST be + /// included within the same Transaction as the system program's + /// `CreateAccount` instruction that creates the account being initialized. + /// Otherwise another party may initialize the account. + /// + /// # Account references + /// 0. [writable] source account to initialize. + InitializeBuffer, + + /// Write program data into a Buffer account. + /// + /// # Account references + /// 0. [writable, signer] Buffer account to write program data to. + Write { + /// Offset at which to write the given bytes. + offset: u32, + /// Serialized program data + #[serde(with = "serde_bytes")] + bytes: Vec, + }, + + /// Deploy an executable program. + /// + /// A program consists of a Program and ProgramData account pair. + /// - The Program account's address will serve as the program id any + /// instructions that execute this program. + /// - The ProgramData account will remain mutable by the loader only and + /// holds the program data and authority information. The ProgramData + /// account's address is derived from the Program account's address and + /// created by the DeployWithMaxDataLen instruction. + /// + /// The ProgramData address is derived from the Program account's address as + /// follows: + /// + /// `let (program_data_address, _) = Pubkey::find_program_address( + /// &[program_address], + /// &bpf_loader_upgradeable::id() + /// );` + /// + /// The `DeployWithMaxDataLen` instruction does not require the ProgramData + /// account be a signer and therefore MUST be included within the same + /// Transaction as the system program's `CreateAccount` instruction that + /// creates the Program account. Otherwise another party may initialize + /// the account. + /// + /// # Account references + /// 0. [Signer] The payer account that will pay to create the ProgramData + /// account. + /// 1. [writable] The uninitialized ProgramData account. + /// 2. [writable] The uninitialized Program account. + /// 3. [writable] The Buffer account where the program data has been + /// written. + /// 4. [] Rent sysvar. + /// 5. [] Clock sysvar. + /// 6. [] System program (`solana_sdk::system_program::id()`). + /// 7. [] The program's authority, optional, if omitted then the program + /// will no longer upgradeable. + DeployWithMaxDataLen { + /// Maximum length that the program can be upgraded to. + max_data_len: usize, + }, + + /// Upgrade a program. + /// + /// A program can be updated as long as the program's authority has not been + /// set to `None`. + /// + /// The Buffer account must contain sufficient lamports to fund the + /// ProgramData account to be rent-exempt, any additional lamports left over + /// will be transferred to the spill, account leaving the Buffer account + /// balance at zero. + /// + /// # Account references + /// 0. [writable] The ProgramData account. + /// 1. [] The Program account. + /// 2. [Writable] The Buffer account where the program data has been + /// written. + /// 3. [writable] The spill account. + /// 4. [] Rent sysvar. + /// 5. [] Clock sysvar. + /// 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. + /// + /// # Account references + /// 0. `[writable]` The 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. + SetAuthority, +} diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 00e3b21c12..c0dca054bf 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -102,6 +102,10 @@ pub mod simple_capitalization { solana_sdk::declare_id!("9r69RnnxABmpcPFfj1yhg4n9YFR2MNaLdKJCC6v3Speb"); } +pub mod bpf_loader_upgradeable_program { + solana_sdk::declare_id!("FbhK8HN9qvNHvJcoFVHAEUCNkagHvu7DTWzdnLuVQ5u4"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -129,6 +133,7 @@ lazy_static! { (rewrite_stake::id(), "rewrite stake"), (filter_stake_delegation_accounts::id(), "filter stake_delegation_accounts #14062"), (simple_capitalization::id(), "simple capitalization"), + (bpf_loader_upgradeable_program::id(), "upgradeable bpf loader"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()