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