1use {
4 crate::{
5 error::FarmError,
6 id::zero,
7 math,
8 pack::check_data_len,
9 program::clock,
10 token::{OraclePrice, OracleType},
11 traits::Packed,
12 },
13 arrayref::{array_ref, array_refs},
14 pyth_client::{PriceStatus, PriceType},
15 solana_program::{
16 account_info::AccountInfo, entrypoint::ProgramResult, msg, program::invoke,
17 program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, system_instruction,
18 sysvar, sysvar::Sysvar,
19 },
20 spl_token::state::{Account, Mint},
21 std::cmp::Ordering,
22};
23
24pub fn get_token_supply(token_mint: &AccountInfo) -> Result<u64, ProgramError> {
27 let data = token_mint.try_borrow_data()?;
28 check_data_len(&data, spl_token::state::Mint::get_packed_len())?;
29 let supply = array_ref![data, 36, 8];
30
31 Ok(u64::from_le_bytes(*supply))
32}
33
34pub fn get_token_decimals(token_mint: &AccountInfo) -> Result<u8, ProgramError> {
37 let data = token_mint.try_borrow_data()?;
38 check_data_len(&data, spl_token::state::Mint::get_packed_len())?;
39 let decimals = array_ref![data, 44, 1];
40
41 Ok(decimals[0])
42}
43
44pub fn get_token_balance(token_account: &AccountInfo) -> Result<u64, ProgramError> {
47 let data = token_account.try_borrow_data()?;
48 check_data_len(&data, spl_token::state::Account::get_packed_len())?;
49 let amount = array_ref![data, 64, 8];
50
51 Ok(u64::from_le_bytes(*amount))
52}
53
54pub fn get_token_account_owner(token_account: &AccountInfo) -> Result<Pubkey, ProgramError> {
57 let data = token_account.try_borrow_data()?;
58 check_data_len(&data, spl_token::state::Account::get_packed_len())?;
59 let owner = array_ref![data, 32, 32];
60
61 Ok(Pubkey::new_from_array(*owner))
62}
63
64pub fn check_token_account_owner(
66 token_account: &AccountInfo,
67 expected_owner: &Pubkey,
68) -> Result<bool, ProgramError> {
69 Ok(token_account.owner == &spl_token::id()
70 && get_token_account_owner(token_account)? == *expected_owner)
71}
72
73pub fn check_token_account_owner_or_zero(
75 token_account: &AccountInfo,
76 expected_owner: &Pubkey,
77) -> Result<bool, ProgramError> {
78 Ok(token_account.key == &zero::id()
79 || (token_account.owner == &spl_token::id()
80 && get_token_account_owner(token_account)? == *expected_owner))
81}
82
83pub fn get_token_account_mint(token_account: &AccountInfo) -> Result<Pubkey, ProgramError> {
86 let data = token_account.try_borrow_data()?;
87 check_data_len(&data, spl_token::state::Account::get_packed_len())?;
88 let mint = array_ref![data, 0, 32];
89
90 Ok(Pubkey::new_from_array(*mint))
91}
92
93pub fn get_mint_authority(token_mint: &AccountInfo) -> Result<Option<Pubkey>, ProgramError> {
96 let data = token_mint.try_borrow_data()?;
97 check_data_len(&data, spl_token::state::Mint::get_packed_len())?;
98
99 let data = array_ref![data, 0, 36];
100 let (tag, authority) = array_refs![data, 4, 32];
101 match *tag {
102 [0, 0, 0, 0] => Ok(None),
103 [1, 0, 0, 0] => Ok(Some(Pubkey::new_from_array(*authority))),
104 _ => Err(ProgramError::InvalidAccountData),
105 }
106}
107
108pub fn check_mint_authority(
110 mint_account: &AccountInfo,
111 expected_authority: Option<Pubkey>,
112) -> Result<bool, ProgramError> {
113 Ok(mint_account.owner == &spl_token::id()
114 && get_mint_authority(mint_account)? == expected_authority)
115}
116
117pub fn is_empty(account: &AccountInfo) -> Result<bool, ProgramError> {
118 Ok(account.data_is_empty() || account.try_lamports()? == 0)
119}
120
121pub fn exists(account: &AccountInfo) -> Result<bool, ProgramError> {
122 Ok(account.try_lamports()? > 0)
123}
124
125pub fn get_balance_increase(
126 account: &AccountInfo,
127 previous_balance: u64,
128) -> Result<u64, ProgramError> {
129 let balance = get_token_balance(account)?;
130 if let Some(res) = balance.checked_sub(previous_balance) {
131 Ok(res)
132 } else {
133 msg!(
134 "Error: Balance decrease was not expected. Account: {}",
135 account.key
136 );
137 Err(FarmError::UnexpectedBalanceDecrease.into())
138 }
139}
140
141pub fn get_balance_decrease(
142 account: &AccountInfo,
143 previous_balance: u64,
144) -> Result<u64, ProgramError> {
145 let balance = get_token_balance(account)?;
146 if let Some(res) = previous_balance.checked_sub(balance) {
147 Ok(res)
148 } else {
149 msg!(
150 "Error: Balance increase was not expected. Account: {}",
151 account.key
152 );
153 Err(FarmError::UnexpectedBalanceIncrease.into())
154 }
155}
156
157pub fn check_tokens_spent(
158 account: &AccountInfo,
159 previous_balance: u64,
160 max_amount_spent: u64,
161) -> Result<u64, ProgramError> {
162 let tokens_spent = get_balance_decrease(account, previous_balance)?;
163 if tokens_spent > max_amount_spent {
164 msg!(
165 "Error: Invoked program overspent. Account: {}, max expected: {}, actual: {}",
166 account.key,
167 max_amount_spent,
168 tokens_spent
169 );
170 Err(FarmError::ProgramOverspent.into())
171 } else {
172 Ok(tokens_spent)
173 }
174}
175
176pub fn check_tokens_received(
177 account: &AccountInfo,
178 previous_balance: u64,
179 min_amount_received: u64,
180) -> Result<u64, ProgramError> {
181 let tokens_received = get_balance_increase(account, previous_balance)?;
182 if tokens_received < min_amount_received {
183 msg!(
184 "Error: Not enough tokens returned by invoked program. Account: {}, min expected: {}, actual: {}",
185 account.key,
186 min_amount_received,
187 tokens_received
188 );
189 Err(FarmError::ProgramInsufficientTransfer.into())
190 } else {
191 Ok(tokens_received)
192 }
193}
194
195pub fn get_token_mint(token_mint: &AccountInfo) -> Result<Mint, ProgramError> {
197 let data = token_mint.try_borrow_data()?;
198 Mint::unpack(&data)
199}
200
201pub fn get_token_account(token_account: &AccountInfo) -> Result<Account, ProgramError> {
203 let data = token_account.try_borrow_data()?;
204 Account::unpack(&data)
205}
206
207pub fn get_token_ratio<'a, 'b>(
209 token_a_balance: u64,
210 token_b_balance: u64,
211 token_a_mint: &'a AccountInfo<'b>,
212 token_b_mint: &'a AccountInfo<'b>,
213) -> Result<f64, ProgramError> {
214 get_token_ratio_with_decimals(
215 token_a_balance,
216 token_b_balance,
217 get_token_decimals(token_a_mint)?,
218 get_token_decimals(token_b_mint)?,
219 )
220}
221
222pub fn get_token_ratio_with_decimals(
224 token_a_balance: u64,
225 token_b_balance: u64,
226 token_a_decimals: u8,
227 token_b_decimals: u8,
228) -> Result<f64, ProgramError> {
229 if token_a_balance == 0 || token_b_balance == 0 {
230 return Ok(0.0);
231 }
232
233 Ok(token_b_balance as f64 / token_a_balance as f64
234 * math::checked_powi(10.0, token_a_decimals as i32 - token_b_decimals as i32)?)
235}
236
237pub fn get_token_pair_ratio<'a, 'b>(
239 token_a_account: &'a AccountInfo<'b>,
240 token_b_account: &'a AccountInfo<'b>,
241) -> Result<f64, ProgramError> {
242 let token_a_balance = get_token_balance(token_a_account)?;
243 let token_b_balance = get_token_balance(token_b_account)?;
244 if token_a_balance == 0 || token_b_balance == 0 {
245 return Ok(0.0);
246 }
247 Ok(token_b_balance as f64 / token_a_balance as f64)
248}
249
250pub fn to_ui_amount(amount: u64, decimals: u8) -> f64 {
251 let mut ui_amount = amount as f64;
252 for _ in 0..decimals {
253 ui_amount /= 10.0;
254 }
255 ui_amount
256}
257
258pub fn to_token_amount(ui_amount: f64, decimals: u8) -> Result<u64, ProgramError> {
259 let mut amount = ui_amount;
260 for _ in 0..decimals {
261 amount *= 10.0;
262 }
263 math::checked_as_u64(amount)
264}
265
266pub fn to_amount_with_new_decimals(
267 amount: u64,
268 original_decimals: u8,
269 new_decimals: u8,
270) -> Result<u64, ProgramError> {
271 match new_decimals.cmp(&original_decimals) {
272 Ordering::Greater => {
273 let exponent = new_decimals.checked_sub(original_decimals).unwrap();
274 math::checked_mul(amount, math::checked_pow(10u64, exponent as usize)?)
275 }
276 Ordering::Less => {
277 let exponent = original_decimals.checked_sub(new_decimals).unwrap();
278 math::checked_div(amount, math::checked_pow(10u64, exponent as usize)?)
279 }
280 Ordering::Equal => Ok(amount),
281 }
282}
283
284pub fn init_token_account<'a, 'b>(
285 funding_account: &'a AccountInfo<'b>,
286 target_account: &'a AccountInfo<'b>,
287 mint_account: &'a AccountInfo<'b>,
288 owner_account: &'a AccountInfo<'b>,
289 rent_program: &'a AccountInfo<'b>,
290 seed: &str,
291) -> ProgramResult {
292 if exists(target_account)? {
293 if !check_token_account_owner(target_account, owner_account.key)? {
294 return Err(ProgramError::IllegalOwner);
295 }
296 if target_account.data_len() != spl_token::state::Account::get_packed_len()
297 || mint_account.key != &get_token_account_mint(target_account)?
298 {
299 return Err(ProgramError::InvalidAccountData);
300 }
301 return Ok(());
302 }
303
304 init_system_account(
305 funding_account,
306 target_account,
307 &spl_token::id(),
308 seed,
309 spl_token::state::Account::get_packed_len(),
310 )?;
311
312 invoke(
313 &spl_token::instruction::initialize_account(
314 &spl_token::id(),
315 target_account.key,
316 mint_account.key,
317 owner_account.key,
318 )?,
319 &[
320 target_account.clone(),
321 mint_account.clone(),
322 owner_account.clone(),
323 rent_program.clone(),
324 ],
325 )
326}
327
328pub fn close_token_account<'a, 'b>(
329 receiving_account: &'a AccountInfo<'b>,
330 target_account: &'a AccountInfo<'b>,
331 authority_account: &'a AccountInfo<'b>,
332) -> ProgramResult {
333 if !exists(target_account)? {
334 return Ok(());
335 }
336
337 invoke(
338 &spl_token::instruction::close_account(
339 &spl_token::id(),
340 target_account.key,
341 receiving_account.key,
342 authority_account.key,
343 &[],
344 )?,
345 &[
346 target_account.clone(),
347 receiving_account.clone(),
348 authority_account.clone(),
349 ],
350 )
351}
352
353pub fn transfer_sol_from_owned<'a, 'b>(
354 program_owned_source_account: &'a AccountInfo<'b>,
355 destination_account: &'a AccountInfo<'b>,
356 amount: u64,
357) -> ProgramResult {
358 **destination_account.try_borrow_mut_lamports()? = destination_account
359 .try_lamports()?
360 .checked_add(amount)
361 .ok_or(ProgramError::InsufficientFunds)?;
362 let source_balance = program_owned_source_account.try_lamports()?;
363 if source_balance < amount {
364 msg!(
365 "Error: Not enough funds to withdraw {} lamports from {}",
366 amount,
367 program_owned_source_account.key
368 );
369 return Err(ProgramError::InsufficientFunds);
370 }
371 **program_owned_source_account.try_borrow_mut_lamports()? = source_balance
372 .checked_sub(amount)
373 .ok_or(ProgramError::InsufficientFunds)?;
374
375 Ok(())
376}
377
378pub fn transfer_sol<'a, 'b>(
379 source_account: &'a AccountInfo<'b>,
380 destination_account: &'a AccountInfo<'b>,
381 amount: u64,
382) -> ProgramResult {
383 if source_account.try_lamports()? < amount {
384 msg!(
385 "Error: Not enough funds to withdraw {} lamports from {}",
386 amount,
387 source_account.key
388 );
389 return Err(ProgramError::InsufficientFunds);
390 }
391 invoke(
392 &system_instruction::transfer(source_account.key, destination_account.key, amount),
393 &[source_account.clone(), destination_account.clone()],
394 )
395}
396
397pub fn transfer_tokens<'a, 'b>(
398 source_account: &'a AccountInfo<'b>,
399 destination_account: &'a AccountInfo<'b>,
400 authority_account: &'a AccountInfo<'b>,
401 amount: u64,
402) -> ProgramResult {
403 invoke(
404 &spl_token::instruction::transfer(
405 &spl_token::id(),
406 source_account.key,
407 destination_account.key,
408 authority_account.key,
409 &[],
410 amount,
411 )?,
412 &[
413 source_account.clone(),
414 destination_account.clone(),
415 authority_account.clone(),
416 ],
417 )
418}
419
420pub fn burn_tokens<'a, 'b>(
421 from_token_account: &'a AccountInfo<'b>,
422 mint_account: &'a AccountInfo<'b>,
423 authority_account: &'a AccountInfo<'b>,
424 amount: u64,
425) -> ProgramResult {
426 invoke(
427 &spl_token::instruction::burn(
428 &spl_token::id(),
429 from_token_account.key,
430 mint_account.key,
431 authority_account.key,
432 &[],
433 amount,
434 )?,
435 &[
436 from_token_account.clone(),
437 mint_account.clone(),
438 authority_account.clone(),
439 ],
440 )
441}
442
443pub fn approve_delegate<'a, 'b>(
444 source_account: &'a AccountInfo<'b>,
445 delegate_account: &'a AccountInfo<'b>,
446 authority_account: &'a AccountInfo<'b>,
447 amount: u64,
448) -> ProgramResult {
449 invoke(
450 &spl_token::instruction::approve(
451 &spl_token::id(),
452 source_account.key,
453 delegate_account.key,
454 authority_account.key,
455 &[],
456 amount,
457 )?,
458 &[
459 source_account.clone(),
460 delegate_account.clone(),
461 authority_account.clone(),
462 ],
463 )
464}
465
466pub fn revoke_delegate<'a, 'b>(
467 source_account: &'a AccountInfo<'b>,
468 authority_account: &'a AccountInfo<'b>,
469) -> ProgramResult {
470 invoke(
471 &spl_token::instruction::revoke(
472 &spl_token::id(),
473 source_account.key,
474 authority_account.key,
475 &[],
476 )?,
477 &[source_account.clone(), authority_account.clone()],
478 )
479}
480
481pub fn init_system_account<'a, 'b>(
482 funding_account: &'a AccountInfo<'b>,
483 target_account: &'a AccountInfo<'b>,
484 owner_key: &Pubkey,
485 seed: &str,
486 data_size: usize,
487) -> ProgramResult {
488 if exists(target_account)? {
489 if target_account.owner != owner_key {
490 return Err(ProgramError::IllegalOwner);
491 }
492 if target_account.data_len() != data_size {
493 return Err(ProgramError::InvalidAccountData);
494 }
495 return Ok(());
496 }
497
498 let derived_account = Pubkey::create_with_seed(funding_account.key, seed, owner_key)?;
499 if target_account.key != &derived_account {
500 return Err(ProgramError::InvalidSeeds);
501 }
502
503 let min_balance = sysvar::rent::Rent::get()
504 .unwrap()
505 .minimum_balance(data_size);
506 invoke(
507 &system_instruction::create_account_with_seed(
508 funding_account.key,
509 target_account.key,
510 funding_account.key,
511 seed,
512 min_balance,
513 data_size as u64,
514 owner_key,
515 ),
516 &[funding_account.clone(), target_account.clone()],
517 )
518}
519
520pub fn close_system_account<'a, 'b>(
521 receiving_account: &'a AccountInfo<'b>,
522 target_account: &'a AccountInfo<'b>,
523 authority_account: &Pubkey,
524) -> ProgramResult {
525 if *target_account.owner != *authority_account {
526 return Err(ProgramError::IllegalOwner);
527 }
528 let cur_balance = target_account.try_lamports()?;
529 transfer_sol_from_owned(target_account, receiving_account, cur_balance)?;
530
531 if target_account.data_len() > 2000 {
532 target_account.try_borrow_mut_data()?[..2000].fill(0);
533 } else {
534 target_account.try_borrow_mut_data()?.fill(0);
535 }
536
537 Ok(())
538}
539
540pub fn get_oracle_price(
541 oracle_type: OracleType,
542 oracle_account: &AccountInfo,
543 max_price_error: f64,
544 max_price_age_sec: u64,
545) -> Result<OraclePrice, ProgramError> {
546 match oracle_type {
547 OracleType::Pyth => get_pyth_price(oracle_account, max_price_error, max_price_age_sec),
548 _ => Err(ProgramError::UnsupportedSysvar),
549 }
550}
551
552pub fn get_pyth_price(
553 pyth_price_info: &AccountInfo,
554 max_price_error: f64,
555 max_price_age_sec: u64,
556) -> Result<OraclePrice, ProgramError> {
557 if is_empty(pyth_price_info)? {
558 msg!("Error: Invalid Pyth oracle account");
559 return Err(FarmError::OracleInvalidAccount.into());
560 }
561
562 let pyth_price_data = &pyth_price_info.try_borrow_data()?;
563 let pyth_price = pyth_client::load_price(pyth_price_data)?;
564
565 if !matches!(pyth_price.agg.status, PriceStatus::Trading)
566 || !matches!(pyth_price.ptype, PriceType::Price)
567 {
568 msg!("Error: Pyth oracle price has invalid state");
569 return Err(FarmError::OracleInvalidState.into());
570 }
571
572 let last_update_age_sec = math::checked_mul(
573 math::checked_sub(clock::get_slot()?, pyth_price.valid_slot)?,
574 solana_program::clock::DEFAULT_MS_PER_SLOT,
575 )? / 1000;
576 if last_update_age_sec > max_price_age_sec {
577 msg!("Error: Pyth oracle price is stale");
578 return Err(FarmError::OracleStalePrice.into());
579 }
580
581 if pyth_price.agg.price <= 0
582 || pyth_price.agg.conf as f64 / pyth_price.agg.price as f64 > max_price_error
583 {
584 msg!("Error: Pyth oracle price is out of bounds");
585 return Err(FarmError::OracleInvalidPrice.into());
586 }
587
588 Ok(OraclePrice {
589 price: pyth_price.agg.price as u64,
591 exponent: pyth_price.expo,
592 })
593}
594
595pub fn get_asset_value_usd(
597 amount: u64,
598 decimals: u8,
599 oracle_type: OracleType,
600 oracle_account: &AccountInfo,
601 max_price_error: f64,
602 max_price_age_sec: u64,
603) -> Result<f64, ProgramError> {
604 if amount == 0 {
605 return Ok(0.0);
606 }
607 let oracle_price = get_oracle_price(
608 oracle_type,
609 oracle_account,
610 max_price_error,
611 max_price_age_sec,
612 )?;
613
614 Ok(amount as f64 * oracle_price.price as f64
615 / math::checked_powi(10.0, decimals as i32 - oracle_price.exponent)?)
616}
617
618pub fn get_asset_value_tokens(
620 usd_amount: f64,
621 token_decimals: u8,
622 oracle_type: OracleType,
623 oracle_account: &AccountInfo,
624 max_price_error: f64,
625 max_price_age_sec: u64,
626) -> Result<u64, ProgramError> {
627 if usd_amount == 0.0 {
628 return Ok(0);
629 }
630 let oracle_price = get_oracle_price(
631 oracle_type,
632 oracle_account,
633 max_price_error,
634 max_price_age_sec,
635 )?;
636
637 math::checked_as_u64(
639 usd_amount as f64 / oracle_price.price as f64
640 * math::checked_powi(10.0, token_decimals as i32 - oracle_price.exponent)?,
641 )
642}
643
644pub fn unpack<T: Packed>(account: &AccountInfo, name: &str) -> Result<T, ProgramError> {
645 if let Ok(object) = T::unpack(&account.try_borrow_data()?) {
646 Ok(object)
647 } else {
648 msg!("Error: Failed to load {} metadata", name);
649 Err(ProgramError::InvalidAccountData)
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656 use spl_token::state::{Account, Mint};
657
658 #[test]
659 fn test_mint_supply_offset() {
660 let mint = Mint {
661 supply: 1234567891011,
662 ..Mint::default()
663 };
664 let mut packed: [u8; 82] = [0; 82];
665 Mint::pack(mint, &mut packed).unwrap();
666
667 let supply = array_ref![packed, 36, 8];
668 assert_eq!(1234567891011, u64::from_le_bytes(*supply));
669 }
670
671 #[test]
672 fn test_mint_decimals_offset() {
673 let mint = Mint {
674 decimals: 123,
675 ..Mint::default()
676 };
677 let mut packed: [u8; 82] = [0; 82];
678 Mint::pack(mint, &mut packed).unwrap();
679
680 let decimals = array_ref![packed, 44, 1];
681 assert_eq!(123, decimals[0]);
682 }
683
684 #[test]
685 fn test_account_amount_offset() {
686 let account = Account {
687 amount: 1234567891011,
688 ..Account::default()
689 };
690 let mut packed: [u8; 165] = [0; 165];
691 Account::pack(account, &mut packed).unwrap();
692
693 let amount = array_ref![packed, 64, 8];
694 assert_eq!(1234567891011, u64::from_le_bytes(*amount));
695 }
696}