web3_utils/
pyth.rs

1use solana_program::{
2    account_info::AccountInfo,
3    clock::Clock,
4    msg,
5    program_error::ProgramError,
6    pubkey::Pubkey,
7    pubkey,
8};
9use pyth_solana_receiver_sdk;
10use std::convert::TryInto;
11
12use crate::{
13    check::{check_account_key},
14    price_update::OriginSolanaPriceUpdateV2,
15};
16
17pub const PYTH_SOL_USD_FEED: Pubkey = pubkey!("7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE");
18
19pub const PRICE_FEED_DISCRIMATOR: [u8; 8] = [34, 241, 35, 99, 157, 126, 244, 205];
20
21pub fn parse_price(data: &[u8]) -> Result<OriginSolanaPriceUpdateV2, ProgramError> {
22    let suffix = &data[..8];
23    if suffix != PRICE_FEED_DISCRIMATOR {
24        msg!("discrimator err");
25        return Err(ProgramError::InvalidArgument);
26    }
27    Ok(OriginSolanaPriceUpdateV2::new(data)?)
28}
29
30/// 获取 Pyth SOL/USD 价格,返回的是 FP32 定点数
31pub fn get_oracle_price_fp32(
32    account: &AccountInfo,
33    clock: &Clock,
34    maximum_age: u64,
35) -> Result<u128, ProgramError> {
36    check_account_key(account, &PYTH_SOL_USD_FEED)?;
37    let data = &account.data.borrow();
38    let update = parse_price(data)?;
39
40    let actual_feed_id = update.0.price_message.feed_id;
41
42    let pyth_solana_receiver_sdk::price_update::Price { price, exponent, .. } =
43        update.0
44            .get_price_no_older_than(clock, maximum_age, &actual_feed_id)
45            .map_err(|e| {
46                msg!("pyth error: {:?}", e);
47                ProgramError::InvalidArgument
48            })?;
49
50    // price is the integar, and the exponent is the power
51    // such as Price: 111111, exponent: -4 => 11.1111 usdt
52    let raw_price = price as i128;
53
54    
55    let fp32_price = if exponent < 0 {
56        (raw_price << 32) / 10i128.pow((-exponent) as u32)
57    } else {
58        (raw_price << 32) * 10i128.pow(exponent as u32)
59    };
60
61    Ok(fp32_price as u128)
62}
63
64pub fn cal_usd_to_sol(
65    usd_decimal: u64,
66    fp32_sol_price: u128,
67) -> u64 {
68    let lamports = (usd_decimal as u128)
69        .checked_mul(1_000_000_000u128) 
70        .unwrap()
71        .checked_shl(32)               
72        .unwrap()
73        / fp32_sol_price;              
74
75    lamports as u64
76}
77
78/// 输入:域名价格 (单位 USD,对标 lamports) 
79/// 输出:对应 lamports (u64)
80pub fn get_domain_price_sol(
81    domain_price_usd: u64,
82    sol_pyth_feed_account: &AccountInfo,
83    clock: &Clock,
84) -> Result<u64, ProgramError> {
85    #[cfg(feature = "devnet")]
86    let query_deviation = 600_000;
87    #[cfg(not(feature = "devnet"))]
88    let query_deviation = 60;
89
90    let sol_price_fp32 = get_oracle_price_fp32(sol_pyth_feed_account, clock, query_deviation)?;
91
92    // lamports = (domain_usd << 32) / (sol_usd_price_fp32 / 1e9)
93    // (domain_usd * 1e9) / sol_price(USD)
94    let lamports = cal_usd_to_sol(domain_price_usd, sol_price_fp32);
95
96    msg!("{:?} usd = {:?} lamports", domain_price_usd, lamports);
97
98    Ok(lamports.try_into().map_err(|_| ProgramError::InvalidArgument)?)
99}