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::instructions::events;
6use crate::state::{engine_from_account_data, header_from_account_data, MARKET_ACCOUNT_SIZE};
7
8const MAX_PRICE_AGE_SECS: i64 = 60;
10
11const PYTH_PROGRAM_ID: Pubkey = pubkey!("FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH");
13
14#[derive(Accounts)]
15pub struct Crank<'info> {
16 pub cranker: Signer<'info>,
18
19 #[account(
21 mut,
22 owner = crate::ID @ PercolatorError::AccountNotFound,
23 constraint = market.data_len() >= MARKET_ACCOUNT_SIZE @ PercolatorError::AccountNotFound,
24 )]
25 pub market: UncheckedAccount<'info>,
26
27 #[account(
29 owner = PYTH_PROGRAM_ID @ PercolatorError::InvalidOraclePrice,
30 )]
31 pub oracle: UncheckedAccount<'info>,
32}
33
34pub fn handler(ctx: Context<Crank>, funding_rate: i64) -> Result<()> {
35 {
37 let data = ctx.accounts.market.try_borrow_data()?;
38 require!(&data[0..8] == b"percmrkt", PercolatorError::AccountNotFound);
39 let header = header_from_account_data(&data)?;
40 require!(
41 header.oracle == ctx.accounts.oracle.key(),
42 PercolatorError::InvalidOraclePrice
43 );
44 }
45
46 let oracle_data = ctx.accounts.oracle.data.borrow();
49 let price_account = load_price_account::<32, ()>(&oracle_data)
50 .map_err(|_| error!(PercolatorError::InvalidOraclePrice))?;
51
52 require!(
54 price_account.agg.status == PriceStatus::Trading,
55 PercolatorError::InvalidOraclePrice
56 );
57
58 let clock = Clock::get()?;
60 let current_timestamp = clock.unix_timestamp;
61 let price_age = current_timestamp
62 .checked_sub(price_account.timestamp)
63 .ok_or_else(|| error!(PercolatorError::StaleOracle))?;
64 require!(price_age <= MAX_PRICE_AGE_SECS, PercolatorError::StaleOracle);
65
66 let price = price_account.agg.price;
67 let expo = price_account.expo;
68
69 require!(price > 0, PercolatorError::InvalidOraclePriceValue);
71
72 require!(expo >= -18 && expo <= 18, PercolatorError::InvalidOraclePrice);
74
75 let oracle_price = if expo >= 0 {
79 (price as u64)
80 .checked_mul(10u64.pow(expo as u32))
81 .ok_or_else(|| error!(PercolatorError::InvalidOraclePriceValue))?
82 } else {
83 let divisor = 10u64.pow((-expo) as u32);
84 (price as u64)
85 .checked_div(divisor)
86 .ok_or_else(|| error!(PercolatorError::InvalidOraclePriceValue))?
87 };
88
89 require!(oracle_price > 0, PercolatorError::InvalidOraclePriceValue);
90
91 drop(oracle_data);
93
94 let market = &ctx.accounts.market;
96 let mut data = market.try_borrow_mut_data()?;
97
98 require!(
99 &data[0..8] == b"percmrkt",
100 PercolatorError::AccountNotFound
101 );
102
103 let engine = engine_from_account_data(&mut data);
104
105 engine
106 .keeper_crank_not_atomic(clock.slot, oracle_price, &[], 0, funding_rate)
107 .map_err(from_risk_error)?;
108
109 emit!(events::Cranked {
110 cranker: ctx.accounts.cranker.key(),
111 oracle_price,
112 slot: clock.slot,
113 });
114
115 Ok(())
116}