1use {
2 crate::{
3 checks::{check_account_for_balance_with_commitment, get_fee_for_messages},
4 cli::CliError,
5 compute_budget::{simulate_and_update_compute_unit_limit, UpdateComputeUnitLimitResult},
6 },
7 clap::ArgMatches,
8 solana_clap_utils::{
9 compute_budget::ComputeUnitLimit, input_parsers::lamports_of_sol, offline::SIGN_ONLY_ARG,
10 },
11 solana_commitment_config::CommitmentConfig,
12 solana_hash::Hash,
13 solana_message::Message,
14 solana_native_token::lamports_to_sol,
15 solana_pubkey::Pubkey,
16 solana_rpc_client::rpc_client::RpcClient,
17};
18
19#[derive(Debug, PartialEq, Eq, Clone, Copy)]
20pub enum SpendAmount {
21 All,
22 Some(u64),
23 RentExempt,
24 AllForAccountCreation { create_account_min_balance: u64 },
25}
26
27impl Default for SpendAmount {
28 fn default() -> Self {
29 Self::Some(u64::default())
30 }
31}
32
33impl SpendAmount {
34 pub fn new(amount: Option<u64>, sign_only: bool) -> Self {
35 match amount {
36 Some(lamports) => Self::Some(lamports),
37 None if !sign_only => Self::All,
38 _ => panic!("ALL amount not supported for sign-only operations"),
39 }
40 }
41
42 pub fn new_from_matches(matches: &ArgMatches<'_>, name: &str) -> Self {
43 let amount = lamports_of_sol(matches, name);
44 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
45 SpendAmount::new(amount, sign_only)
46 }
47}
48
49struct SpendAndFee {
50 spend: u64,
51 fee: u64,
52}
53
54pub fn resolve_spend_tx_and_check_account_balance<F>(
55 rpc_client: &RpcClient,
56 sign_only: bool,
57 amount: SpendAmount,
58 blockhash: &Hash,
59 from_pubkey: &Pubkey,
60 compute_unit_limit: ComputeUnitLimit,
61 build_message: F,
62 commitment: CommitmentConfig,
63) -> Result<(Message, u64), CliError>
64where
65 F: Fn(u64) -> Message,
66{
67 resolve_spend_tx_and_check_account_balances(
68 rpc_client,
69 sign_only,
70 amount,
71 blockhash,
72 from_pubkey,
73 from_pubkey,
74 compute_unit_limit,
75 build_message,
76 commitment,
77 )
78}
79
80pub fn resolve_spend_tx_and_check_account_balances<F>(
81 rpc_client: &RpcClient,
82 sign_only: bool,
83 amount: SpendAmount,
84 blockhash: &Hash,
85 from_pubkey: &Pubkey,
86 fee_pubkey: &Pubkey,
87 compute_unit_limit: ComputeUnitLimit,
88 build_message: F,
89 commitment: CommitmentConfig,
90) -> Result<(Message, u64), CliError>
91where
92 F: Fn(u64) -> Message,
93{
94 if sign_only {
95 let (message, SpendAndFee { spend, fee: _ }) = resolve_spend_message(
96 rpc_client,
97 amount,
98 None,
99 0,
100 from_pubkey,
101 fee_pubkey,
102 0,
103 compute_unit_limit,
104 build_message,
105 )?;
106 Ok((message, spend))
107 } else {
108 let from_balance = rpc_client
109 .get_balance_with_commitment(from_pubkey, commitment)?
110 .value;
111 let from_rent_exempt_minimum = if amount == SpendAmount::RentExempt {
112 let data = rpc_client.get_account_data(from_pubkey)?;
113 rpc_client.get_minimum_balance_for_rent_exemption(data.len())?
114 } else {
115 0
116 };
117 let (message, SpendAndFee { spend, fee }) = resolve_spend_message(
118 rpc_client,
119 amount,
120 Some(blockhash),
121 from_balance,
122 from_pubkey,
123 fee_pubkey,
124 from_rent_exempt_minimum,
125 compute_unit_limit,
126 build_message,
127 )?;
128 if from_pubkey == fee_pubkey {
129 if from_balance == 0 || from_balance < spend.saturating_add(fee) {
130 return Err(CliError::InsufficientFundsForSpendAndFee(
131 lamports_to_sol(spend),
132 lamports_to_sol(fee),
133 *from_pubkey,
134 ));
135 }
136 } else {
137 if from_balance < spend {
138 return Err(CliError::InsufficientFundsForSpend(
139 lamports_to_sol(spend),
140 *from_pubkey,
141 ));
142 }
143 if !check_account_for_balance_with_commitment(rpc_client, fee_pubkey, fee, commitment)?
144 {
145 return Err(CliError::InsufficientFundsForFee(
146 lamports_to_sol(fee),
147 *fee_pubkey,
148 ));
149 }
150 }
151 Ok((message, spend))
152 }
153}
154
155fn resolve_spend_message<F>(
156 rpc_client: &RpcClient,
157 amount: SpendAmount,
158 blockhash: Option<&Hash>,
159 from_balance: u64,
160 from_pubkey: &Pubkey,
161 fee_pubkey: &Pubkey,
162 from_rent_exempt_minimum: u64,
163 compute_unit_limit: ComputeUnitLimit,
164 build_message: F,
165) -> Result<(Message, SpendAndFee), CliError>
166where
167 F: Fn(u64) -> Message,
168{
169 let (fee, compute_unit_info) = match blockhash {
170 Some(blockhash) => {
171 let lamports = if from_pubkey == fee_pubkey {
182 match amount {
183 SpendAmount::Some(lamports) => lamports,
184 SpendAmount::AllForAccountCreation {
185 create_account_min_balance,
186 } => create_account_min_balance,
187 SpendAmount::All | SpendAmount::RentExempt => 0,
188 }
189 } else {
190 match amount {
191 SpendAmount::Some(lamports) => lamports,
192 SpendAmount::AllForAccountCreation { .. } | SpendAmount::All => from_balance,
193 SpendAmount::RentExempt => {
194 from_balance.saturating_sub(from_rent_exempt_minimum)
195 }
196 }
197 };
198 let mut dummy_message = build_message(lamports);
199
200 dummy_message.recent_blockhash = *blockhash;
201 let compute_unit_info =
202 if let UpdateComputeUnitLimitResult::UpdatedInstructionIndex(ix_index) =
203 simulate_and_update_compute_unit_limit(
204 &compute_unit_limit,
205 rpc_client,
206 &mut dummy_message,
207 )?
208 {
209 Some((ix_index, dummy_message.instructions[ix_index].data.clone()))
210 } else {
211 None
212 };
213 (
214 get_fee_for_messages(rpc_client, &[&dummy_message])?,
215 compute_unit_info,
216 )
217 }
218 None => (0, None), };
220
221 let (mut message, spend_and_fee) = match amount {
222 SpendAmount::Some(lamports) => (
223 build_message(lamports),
224 SpendAndFee {
225 spend: lamports,
226 fee,
227 },
228 ),
229 SpendAmount::All | SpendAmount::AllForAccountCreation { .. } => {
230 let lamports = if from_pubkey == fee_pubkey {
231 from_balance.saturating_sub(fee)
232 } else {
233 from_balance
234 };
235 (
236 build_message(lamports),
237 SpendAndFee {
238 spend: lamports,
239 fee,
240 },
241 )
242 }
243 SpendAmount::RentExempt => {
244 let mut lamports = if from_pubkey == fee_pubkey {
245 from_balance.saturating_sub(fee)
246 } else {
247 from_balance
248 };
249 lamports = lamports.saturating_sub(from_rent_exempt_minimum);
250 (
251 build_message(lamports),
252 SpendAndFee {
253 spend: lamports,
254 fee,
255 },
256 )
257 }
258 };
259 if let Some((ix_index, ix_data)) = compute_unit_info {
261 message.instructions[ix_index].data = ix_data;
262 }
263 Ok((message, spend_and_fee))
264}