diff --git a/cli/src/cli.rs b/cli/src/cli.rs index a44c64a4c..412939247 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -49,6 +49,7 @@ use solana_sdk::{ instruction::{Instruction, InstructionError}, loader_instruction, message::Message, + native_token::Sol, pubkey::{Pubkey, MAX_SEED_LEN}, signature::{keypair_from_seed, Keypair, Signature, Signer, SignerError}, signers::Signers, @@ -178,6 +179,7 @@ pub enum CliCommand { program_location: String, address: Option, use_deprecated_loader: bool, + allow_excessive_balance: bool, }, // Stake Commands CreateStakeAccount { @@ -609,13 +611,13 @@ pub fn parse_command( signers.push(signer); 1 }); - let use_deprecated_loader = matches.is_present("use_deprecated_loader"); Ok(CliCommandInfo { command: CliCommand::Deploy { program_location: matches.value_of("program_location").unwrap().to_string(), address, - use_deprecated_loader, + use_deprecated_loader: matches.is_present("use_deprecated_loader"), + allow_excessive_balance: matches.is_present("allow_excessive_balance"), }, signers, }) @@ -1132,6 +1134,7 @@ fn process_deploy( 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 @@ -1145,6 +1148,7 @@ fn process_deploy( program_location, address, use_deprecated_loader, + allow_excessive_balance, new_keypair, ); @@ -1160,7 +1164,7 @@ fn process_deploy( WORDS ); eprintln!( - "then pass it as the [ADDRESS_SIGNER] argument to `solana deploy ...`\n{}\n{}\n{}", + "then pass it as the [PROGRAM_ADDRESS_SIGNER] argument to `solana deploy ...`\n{}\n{}\n{}", divider, phrase, divider ); } @@ -1173,6 +1177,7 @@ fn do_process_deploy( 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 { @@ -1237,6 +1242,15 @@ fn do_process_deploy( 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) } else { @@ -1619,12 +1633,14 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { program_location, address, use_deprecated_loader, + allow_excessive_balance, } => process_deploy( &rpc_client, config, program_location, *address, *use_deprecated_loader, + *allow_excessive_balance, ), // Stake Commands @@ -2241,7 +2257,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .arg( Arg::with_name("address_signer") .index(2) - .value_name("ADDRESS_SIGNER") + .value_name("PROGRAM_ADDRESS_SIGNER") .takes_value(true) .validator(is_valid_signer) .help("The signer for the desired address of the program [default: new random address]") @@ -2253,6 +2269,12 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, ' .hidden(true) // Don't document this argument to discourage its use .help("Use the deprecated BPF loader") ) + .arg( + Arg::with_name("allow_excessive_balance") + .long("allow-excessive-deploy-account-balance") + .takes_value(false) + .help("Use the designated program id, even if the account already holds a large balance of SOL") + ) .arg(commitment_arg_with_default("max")), ) .subcommand( @@ -2621,6 +2643,7 @@ mod tests { program_location: "/Users/test/program.o".to_string(), address: None, use_deprecated_loader: false, + allow_excessive_balance: false, }, signers: vec![read_keypair_file(&keypair_file).unwrap().into()], } @@ -2642,6 +2665,7 @@ mod tests { program_location: "/Users/test/program.o".to_string(), address: Some(1), use_deprecated_loader: false, + allow_excessive_balance: false, }, signers: vec![ read_keypair_file(&keypair_file).unwrap().into(), @@ -2955,6 +2979,7 @@ mod tests { program_location: pathbuf.to_str().unwrap().to_string(), address: None, use_deprecated_loader: false, + allow_excessive_balance: false, }; let result = process_command(&config); let json: Value = serde_json::from_str(&result.unwrap()).unwrap(); @@ -2973,6 +2998,7 @@ mod tests { program_location: "bad/file/location.so".to_string(), address: None, use_deprecated_loader: false, + allow_excessive_balance: false, }; assert!(process_command(&config).is_err()); } diff --git a/cli/tests/deploy.rs b/cli/tests/deploy.rs index 3161e17f8..607b915b5 100644 --- a/cli/tests/deploy.rs +++ b/cli/tests/deploy.rs @@ -55,7 +55,7 @@ fn test_cli_deploy_program() { faucet_host: None, faucet_port: faucet_addr.port(), pubkey: None, - lamports: 3 * minimum_balance_for_rent_exemption, // min balance for rent exemption for two programs + leftover for tx processing + lamports: 4 * minimum_balance_for_rent_exemption, // min balance for rent exemption for three programs + leftover for tx processing }; config.signers = vec![&keypair]; process_command(&config).unwrap(); @@ -64,6 +64,7 @@ fn test_cli_deploy_program() { program_location: pathbuf.to_str().unwrap().to_string(), address: None, use_deprecated_loader: false, + allow_excessive_balance: false, }; let response = process_command(&config); @@ -98,6 +99,7 @@ fn test_cli_deploy_program() { program_location: pathbuf.to_str().unwrap().to_string(), address: Some(1), use_deprecated_loader: false, + allow_excessive_balance: false, }; process_command(&config).unwrap(); let account1 = rpc_client @@ -113,6 +115,44 @@ fn test_cli_deploy_program() { // Attempt to redeploy to the same address process_command(&config).unwrap_err(); + // Attempt to deploy to account with excess balance + let custom_address_keypair = Keypair::new(); + config.command = CliCommand::Airdrop { + faucet_host: None, + faucet_port: faucet_addr.port(), + pubkey: None, + lamports: 2 * minimum_balance_for_rent_exemption, // Anything over minimum_balance_for_rent_exemption should trigger err + }; + config.signers = vec![&custom_address_keypair]; + process_command(&config).unwrap(); + + config.signers = vec![&keypair, &custom_address_keypair]; + config.command = CliCommand::Deploy { + program_location: pathbuf.to_str().unwrap().to_string(), + address: Some(1), + use_deprecated_loader: false, + allow_excessive_balance: false, + }; + process_command(&config).unwrap_err(); + + // Use forcing parameter to deploy to account with excess balance + config.command = CliCommand::Deploy { + program_location: pathbuf.to_str().unwrap().to_string(), + address: Some(1), + use_deprecated_loader: false, + allow_excessive_balance: true, + }; + process_command(&config).unwrap(); + let account2 = rpc_client + .get_account_with_commitment(&custom_address_keypair.pubkey(), CommitmentConfig::recent()) + .unwrap() + .value + .unwrap(); + assert_eq!(account2.lamports, 2 * minimum_balance_for_rent_exemption); + assert_eq!(account2.owner, bpf_loader::id()); + assert_eq!(account2.executable, true); + assert_eq!(account0.data, account2.data); + server.close().unwrap(); remove_dir_all(ledger_path).unwrap(); }