percli_program/instructions/
accrue_market.rs1use anchor_lang::prelude::*;
2use pyth_sdk_solana::state::{load_price_account, PriceStatus};
3
4use crate::error::{from_risk_error, PercolatorError};
5use crate::instructions::events;
6use crate::state::{engine_from_account_data, header_from_account_data, MARKET_ACCOUNT_SIZE};
7
8const MAX_PRICE_AGE_SECS: i64 = 60;
9const PYTH_PROGRAM_ID: Pubkey = pubkey!("FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH");
10
11#[derive(Accounts)]
12pub struct AccrueMarket<'info> {
13 pub signer: Signer<'info>,
14
15 #[account(
17 mut,
18 owner = crate::ID @ PercolatorError::AccountNotFound,
19 constraint = market.data_len() >= MARKET_ACCOUNT_SIZE @ PercolatorError::AccountNotFound,
20 )]
21 pub market: UncheckedAccount<'info>,
22
23 #[account(
25 owner = PYTH_PROGRAM_ID @ PercolatorError::InvalidOraclePrice,
26 )]
27 pub oracle: UncheckedAccount<'info>,
28}
29
30pub fn handler(ctx: Context<AccrueMarket>) -> Result<()> {
31 {
33 let data = ctx.accounts.market.try_borrow_data()?;
34 require!(&data[0..8] == b"percmrkt", PercolatorError::AccountNotFound);
35 let header = header_from_account_data(&data)?;
36 require!(
37 header.oracle == ctx.accounts.oracle.key(),
38 PercolatorError::InvalidOraclePrice
39 );
40 }
41
42 let oracle_data = ctx.accounts.oracle.data.borrow();
44 let price_account = load_price_account::<32, ()>(&oracle_data)
45 .map_err(|_| error!(PercolatorError::InvalidOraclePrice))?;
46
47 require!(
48 price_account.agg.status == PriceStatus::Trading,
49 PercolatorError::InvalidOraclePrice
50 );
51
52 let clock = Clock::get()?;
53 let price_age = clock
54 .unix_timestamp
55 .checked_sub(price_account.timestamp)
56 .ok_or_else(|| error!(PercolatorError::StaleOracle))?;
57 require!(price_age <= MAX_PRICE_AGE_SECS, PercolatorError::StaleOracle);
58
59 let price = price_account.agg.price;
60 let expo = price_account.expo;
61
62 require!(price > 0, PercolatorError::InvalidOraclePriceValue);
63 require!(expo >= -18 && expo <= 18, PercolatorError::InvalidOraclePrice);
64
65 let oracle_price = if expo >= 0 {
66 (price as u64)
67 .checked_mul(10u64.pow(expo as u32))
68 .ok_or_else(|| error!(PercolatorError::InvalidOraclePriceValue))?
69 } else {
70 let divisor = 10u64.pow((-expo) as u32);
71 (price as u64)
72 .checked_div(divisor)
73 .ok_or_else(|| error!(PercolatorError::InvalidOraclePriceValue))?
74 };
75
76 require!(oracle_price > 0, PercolatorError::InvalidOraclePriceValue);
77
78 drop(oracle_data);
79
80 let market = &ctx.accounts.market;
82 let mut data = market.try_borrow_mut_data()?;
83
84 require!(
85 &data[0..8] == b"percmrkt",
86 PercolatorError::AccountNotFound
87 );
88
89 let engine = engine_from_account_data(&mut data);
90
91 engine
92 .accrue_market_to(clock.slot, oracle_price)
93 .map_err(from_risk_error)?;
94
95 emit!(events::MarketAccrued {
96 signer: ctx.accounts.signer.key(),
97 oracle_price,
98 slot: clock.slot,
99 });
100
101 Ok(())
102}