2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
crate::cli::CliError,
|
2022-08-24 09:47:02 -07:00
|
|
|
solana_rpc_client::rpc_client::RpcClient,
|
|
|
|
solana_rpc_client_api::client_error::{Error as ClientError, Result as ClientResult},
|
2021-12-03 09:00:31 -08:00
|
|
|
solana_sdk::{
|
|
|
|
commitment_config::CommitmentConfig, message::Message, native_token::lamports_to_sol,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
},
|
2020-05-14 11:24:14 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
pub fn check_account_for_fee(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
message: &Message,
|
|
|
|
) -> Result<(), CliError> {
|
2021-10-13 13:10:58 -07:00
|
|
|
check_account_for_multiple_fees(rpc_client, account_pubkey, &[message])
|
2020-05-14 11:24:14 -07:00
|
|
|
}
|
|
|
|
|
2020-06-17 11:18:48 -07:00
|
|
|
pub fn check_account_for_fee_with_commitment(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
message: &Message,
|
|
|
|
commitment: CommitmentConfig,
|
|
|
|
) -> Result<(), CliError> {
|
|
|
|
check_account_for_multiple_fees_with_commitment(
|
|
|
|
rpc_client,
|
|
|
|
account_pubkey,
|
|
|
|
&[message],
|
|
|
|
commitment,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-05-14 11:24:14 -07:00
|
|
|
pub fn check_account_for_multiple_fees(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
messages: &[&Message],
|
2020-06-17 11:18:48 -07:00
|
|
|
) -> Result<(), CliError> {
|
|
|
|
check_account_for_multiple_fees_with_commitment(
|
|
|
|
rpc_client,
|
|
|
|
account_pubkey,
|
|
|
|
messages,
|
|
|
|
CommitmentConfig::default(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_account_for_multiple_fees_with_commitment(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
messages: &[&Message],
|
|
|
|
commitment: CommitmentConfig,
|
2020-10-16 11:03:50 -07:00
|
|
|
) -> Result<(), CliError> {
|
|
|
|
check_account_for_spend_multiple_fees_with_commitment(
|
|
|
|
rpc_client,
|
|
|
|
account_pubkey,
|
|
|
|
0,
|
|
|
|
messages,
|
|
|
|
commitment,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_account_for_spend_multiple_fees_with_commitment(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
balance: u64,
|
|
|
|
messages: &[&Message],
|
|
|
|
commitment: CommitmentConfig,
|
2020-05-14 11:24:14 -07:00
|
|
|
) -> Result<(), CliError> {
|
2021-10-29 13:52:59 -07:00
|
|
|
let fee = get_fee_for_messages(rpc_client, messages)?;
|
2022-06-02 02:54:26 -07:00
|
|
|
check_account_for_spend_and_fee_with_commitment(
|
|
|
|
rpc_client,
|
|
|
|
account_pubkey,
|
|
|
|
balance,
|
|
|
|
fee,
|
|
|
|
commitment,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_account_for_spend_and_fee_with_commitment(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
balance: u64,
|
|
|
|
fee: u64,
|
|
|
|
commitment: CommitmentConfig,
|
|
|
|
) -> Result<(), CliError> {
|
2020-10-16 11:03:50 -07:00
|
|
|
if !check_account_for_balance_with_commitment(
|
|
|
|
rpc_client,
|
|
|
|
account_pubkey,
|
|
|
|
balance + fee,
|
|
|
|
commitment,
|
|
|
|
)
|
|
|
|
.map_err(Into::<ClientError>::into)?
|
2020-05-14 11:24:14 -07:00
|
|
|
{
|
2020-10-16 11:03:50 -07:00
|
|
|
if balance > 0 {
|
|
|
|
return Err(CliError::InsufficientFundsForSpendAndFee(
|
|
|
|
lamports_to_sol(balance),
|
|
|
|
lamports_to_sol(fee),
|
2021-03-02 11:13:41 -08:00
|
|
|
*account_pubkey,
|
2020-10-16 11:03:50 -07:00
|
|
|
));
|
|
|
|
} else {
|
2021-03-02 11:13:41 -08:00
|
|
|
return Err(CliError::InsufficientFundsForFee(
|
|
|
|
lamports_to_sol(fee),
|
|
|
|
*account_pubkey,
|
|
|
|
));
|
2020-10-16 11:03:50 -07:00
|
|
|
}
|
2020-05-14 11:24:14 -07:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-29 13:52:59 -07:00
|
|
|
pub fn get_fee_for_messages(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
messages: &[&Message],
|
|
|
|
) -> Result<u64, CliError> {
|
2021-08-13 09:08:20 -07:00
|
|
|
Ok(messages
|
2020-05-14 11:24:14 -07:00
|
|
|
.iter()
|
2021-12-22 23:02:58 -08:00
|
|
|
.map(|message| rpc_client.get_fee_for_message(message))
|
2021-08-13 09:08:20 -07:00
|
|
|
.collect::<Result<Vec<_>, _>>()?
|
|
|
|
.iter()
|
|
|
|
.sum())
|
2020-05-14 11:24:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_account_for_balance(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
balance: u64,
|
|
|
|
) -> ClientResult<bool> {
|
2020-06-17 11:18:48 -07:00
|
|
|
check_account_for_balance_with_commitment(
|
|
|
|
rpc_client,
|
|
|
|
account_pubkey,
|
|
|
|
balance,
|
|
|
|
CommitmentConfig::default(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_account_for_balance_with_commitment(
|
|
|
|
rpc_client: &RpcClient,
|
|
|
|
account_pubkey: &Pubkey,
|
|
|
|
balance: u64,
|
|
|
|
commitment: CommitmentConfig,
|
|
|
|
) -> ClientResult<bool> {
|
|
|
|
let lamports = rpc_client
|
|
|
|
.get_balance_with_commitment(account_pubkey, commitment)?
|
|
|
|
.value;
|
2020-05-14 11:24:14 -07:00
|
|
|
if lamports != 0 && lamports >= balance {
|
|
|
|
return Ok(true);
|
|
|
|
}
|
|
|
|
Ok(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_unique_pubkeys(
|
|
|
|
pubkey0: (&Pubkey, String),
|
|
|
|
pubkey1: (&Pubkey, String),
|
|
|
|
) -> Result<(), CliError> {
|
|
|
|
if pubkey0.0 == pubkey1.0 {
|
|
|
|
Err(CliError::BadParameter(format!(
|
|
|
|
"Identical pubkeys found: `{}` and `{}` must be unique",
|
|
|
|
pubkey0.1, pubkey1.1
|
|
|
|
)))
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-12-03 09:00:31 -08:00
|
|
|
use {
|
|
|
|
super::*,
|
|
|
|
serde_json::json,
|
2022-08-24 09:47:02 -07:00
|
|
|
solana_rpc_client_api::{
|
|
|
|
request::RpcRequest,
|
|
|
|
response::{Response, RpcResponseContext},
|
2021-12-03 09:00:31 -08:00
|
|
|
},
|
|
|
|
solana_sdk::system_instruction,
|
|
|
|
std::collections::HashMap,
|
2020-05-14 11:24:14 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_check_account_for_fees() {
|
|
|
|
let account_balance = 1;
|
|
|
|
let account_balance_response = json!(Response {
|
2022-05-11 21:17:21 -07:00
|
|
|
context: RpcResponseContext {
|
|
|
|
slot: 1,
|
|
|
|
api_version: None
|
|
|
|
},
|
2020-05-14 11:24:14 -07:00
|
|
|
value: json!(account_balance),
|
|
|
|
});
|
2020-10-19 12:12:08 -07:00
|
|
|
let pubkey = solana_sdk::pubkey::new_rand();
|
2020-05-14 11:24:14 -07:00
|
|
|
|
|
|
|
let pubkey0 = Pubkey::new(&[0; 32]);
|
|
|
|
let pubkey1 = Pubkey::new(&[1; 32]);
|
|
|
|
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
2020-06-24 13:52:38 -07:00
|
|
|
let message0 = Message::new(&[ix0], Some(&pubkey0));
|
2020-05-14 11:24:14 -07:00
|
|
|
|
|
|
|
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
|
|
|
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
|
2020-06-24 13:52:38 -07:00
|
|
|
let message1 = Message::new(&[ix0, ix1], Some(&pubkey0));
|
2020-05-14 11:24:14 -07:00
|
|
|
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(RpcRequest::GetBalance, account_balance_response.clone());
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
2021-10-13 13:10:58 -07:00
|
|
|
check_account_for_fee(&rpc_client, &pubkey, &message0).expect("unexpected result");
|
2020-05-14 11:24:14 -07:00
|
|
|
|
2021-08-13 09:08:20 -07:00
|
|
|
let check_fee_response = json!(Response {
|
2022-05-11 21:17:21 -07:00
|
|
|
context: RpcResponseContext {
|
|
|
|
slot: 1,
|
|
|
|
api_version: None
|
|
|
|
},
|
2021-08-13 09:08:20 -07:00
|
|
|
value: json!(2),
|
|
|
|
});
|
2020-05-14 11:24:14 -07:00
|
|
|
let mut mocks = HashMap::new();
|
2021-08-13 09:08:20 -07:00
|
|
|
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
2020-05-14 11:24:14 -07:00
|
|
|
mocks.insert(RpcRequest::GetBalance, account_balance_response.clone());
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
2021-10-13 13:10:58 -07:00
|
|
|
assert!(check_account_for_fee(&rpc_client, &pubkey, &message1).is_err());
|
2020-05-14 11:24:14 -07:00
|
|
|
|
2021-08-13 09:08:20 -07:00
|
|
|
let check_fee_response = json!(Response {
|
2022-05-11 21:17:21 -07:00
|
|
|
context: RpcResponseContext {
|
|
|
|
slot: 1,
|
|
|
|
api_version: None
|
|
|
|
},
|
2021-08-13 09:08:20 -07:00
|
|
|
value: json!(2),
|
|
|
|
});
|
2020-05-14 11:24:14 -07:00
|
|
|
let mut mocks = HashMap::new();
|
2021-08-13 09:08:20 -07:00
|
|
|
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
2020-05-14 11:24:14 -07:00
|
|
|
mocks.insert(RpcRequest::GetBalance, account_balance_response);
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
2021-10-13 13:10:58 -07:00
|
|
|
assert!(
|
|
|
|
check_account_for_multiple_fees(&rpc_client, &pubkey, &[&message0, &message0]).is_err()
|
|
|
|
);
|
2020-05-14 11:24:14 -07:00
|
|
|
|
|
|
|
let account_balance = 2;
|
|
|
|
let account_balance_response = json!(Response {
|
2022-05-11 21:17:21 -07:00
|
|
|
context: RpcResponseContext {
|
|
|
|
slot: 1,
|
|
|
|
api_version: None
|
|
|
|
},
|
2020-05-14 11:24:14 -07:00
|
|
|
value: json!(account_balance),
|
|
|
|
});
|
2021-08-13 09:08:20 -07:00
|
|
|
let check_fee_response = json!(Response {
|
2022-05-11 21:17:21 -07:00
|
|
|
context: RpcResponseContext {
|
|
|
|
slot: 1,
|
|
|
|
api_version: None
|
|
|
|
},
|
2021-08-13 09:08:20 -07:00
|
|
|
value: json!(1),
|
|
|
|
});
|
2020-05-14 11:24:14 -07:00
|
|
|
|
|
|
|
let mut mocks = HashMap::new();
|
2021-08-13 09:08:20 -07:00
|
|
|
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
2020-05-14 11:24:14 -07:00
|
|
|
mocks.insert(RpcRequest::GetBalance, account_balance_response);
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
|
2021-10-13 13:10:58 -07:00
|
|
|
check_account_for_multiple_fees(&rpc_client, &pubkey, &[&message0, &message0])
|
2021-08-13 09:08:20 -07:00
|
|
|
.expect("unexpected result");
|
2020-05-14 11:24:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_check_account_for_balance() {
|
|
|
|
let account_balance = 50;
|
|
|
|
let account_balance_response = json!(Response {
|
2022-05-11 21:17:21 -07:00
|
|
|
context: RpcResponseContext {
|
|
|
|
slot: 1,
|
|
|
|
api_version: None
|
|
|
|
},
|
2020-05-14 11:24:14 -07:00
|
|
|
value: json!(account_balance),
|
|
|
|
});
|
2020-10-19 12:12:08 -07:00
|
|
|
let pubkey = solana_sdk::pubkey::new_rand();
|
2020-05-14 11:24:14 -07:00
|
|
|
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(RpcRequest::GetBalance, account_balance_response);
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
|
2021-05-19 07:31:47 -07:00
|
|
|
assert!(check_account_for_balance(&rpc_client, &pubkey, 1).unwrap());
|
|
|
|
assert!(check_account_for_balance(&rpc_client, &pubkey, account_balance).unwrap());
|
|
|
|
assert!(!check_account_for_balance(&rpc_client, &pubkey, account_balance + 1).unwrap());
|
2020-05-14 11:24:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-10-29 13:52:59 -07:00
|
|
|
fn test_get_fee_for_messages() {
|
2021-08-13 09:08:20 -07:00
|
|
|
let check_fee_response = json!(Response {
|
2022-05-11 21:17:21 -07:00
|
|
|
context: RpcResponseContext {
|
|
|
|
slot: 1,
|
|
|
|
api_version: None
|
|
|
|
},
|
2021-08-13 09:08:20 -07:00
|
|
|
value: json!(1),
|
|
|
|
});
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
2020-05-14 11:24:14 -07:00
|
|
|
|
2021-08-13 09:08:20 -07:00
|
|
|
// No messages, no fee.
|
2021-10-29 13:52:59 -07:00
|
|
|
assert_eq!(get_fee_for_messages(&rpc_client, &[]).unwrap(), 0);
|
2020-05-14 11:24:14 -07:00
|
|
|
|
|
|
|
// One message w/ one signature, a fee.
|
|
|
|
let pubkey0 = Pubkey::new(&[0; 32]);
|
|
|
|
let pubkey1 = Pubkey::new(&[1; 32]);
|
|
|
|
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
2020-06-24 13:52:38 -07:00
|
|
|
let message0 = Message::new(&[ix0], Some(&pubkey0));
|
2021-10-29 13:52:59 -07:00
|
|
|
assert_eq!(get_fee_for_messages(&rpc_client, &[&message0]).unwrap(), 1);
|
2020-05-14 11:24:14 -07:00
|
|
|
|
2021-08-13 09:08:20 -07:00
|
|
|
// No signatures, no fee.
|
|
|
|
let check_fee_response = json!(Response {
|
2022-05-11 21:17:21 -07:00
|
|
|
context: RpcResponseContext {
|
|
|
|
slot: 1,
|
|
|
|
api_version: None
|
|
|
|
},
|
2021-08-13 09:08:20 -07:00
|
|
|
value: json!(0),
|
|
|
|
});
|
|
|
|
let mut mocks = HashMap::new();
|
|
|
|
mocks.insert(RpcRequest::GetFeeForMessage, check_fee_response);
|
|
|
|
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
|
|
|
let message = Message::default();
|
|
|
|
assert_eq!(
|
2021-10-29 13:52:59 -07:00
|
|
|
get_fee_for_messages(&rpc_client, &[&message, &message]).unwrap(),
|
2021-08-13 09:08:20 -07:00
|
|
|
0
|
|
|
|
);
|
2020-05-14 11:24:14 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_check_unique_pubkeys() {
|
2020-10-19 12:12:08 -07:00
|
|
|
let pubkey0 = solana_sdk::pubkey::new_rand();
|
2020-05-15 09:35:43 -07:00
|
|
|
let pubkey_clone = pubkey0;
|
2020-10-19 12:12:08 -07:00
|
|
|
let pubkey1 = solana_sdk::pubkey::new_rand();
|
2020-05-14 11:24:14 -07:00
|
|
|
|
2020-05-15 09:35:43 -07:00
|
|
|
check_unique_pubkeys((&pubkey0, "foo".to_string()), (&pubkey1, "bar".to_string()))
|
|
|
|
.expect("unexpected result");
|
|
|
|
check_unique_pubkeys((&pubkey0, "foo".to_string()), (&pubkey1, "foo".to_string()))
|
|
|
|
.expect("unexpected result");
|
|
|
|
|
2020-05-14 11:24:14 -07:00
|
|
|
assert!(check_unique_pubkeys(
|
|
|
|
(&pubkey0, "foo".to_string()),
|
|
|
|
(&pubkey_clone, "bar".to_string())
|
|
|
|
)
|
|
|
|
.is_err());
|
|
|
|
}
|
|
|
|
}
|