gmsol_store/states/oracle/
feed.rs

1use anchor_lang::prelude::*;
2use bytemuck::Zeroable;
3use gmsol_utils::InitSpace;
4
5use crate::{
6    states::{Seed, TokenConfig},
7    CoreError,
8};
9
10use super::PriceProviderKind;
11
12const MAX_FLAGS: usize = 8;
13
14/// Custom Price Feed.
15#[account(zero_copy)]
16#[cfg_attr(feature = "debug", derive(Debug))]
17pub struct PriceFeed {
18    pub(crate) bump: u8,
19    pub(crate) provider: u8,
20    pub(crate) index: u16,
21    padding_0: [u8; 12],
22    pub(crate) store: Pubkey,
23    /// Authority.
24    pub authority: Pubkey,
25    pub(crate) token: Pubkey,
26    pub(crate) feed_id: Pubkey,
27    last_published_at_slot: u64,
28    last_published_at: i64,
29    price: PriceFeedPrice,
30    reserved: [u8; 256],
31}
32
33impl InitSpace for PriceFeed {
34    const INIT_SPACE: usize = std::mem::size_of::<Self>();
35}
36
37impl Seed for PriceFeed {
38    const SEED: &'static [u8] = b"price_feed";
39}
40
41impl Default for PriceFeed {
42    fn default() -> Self {
43        Zeroable::zeroed()
44    }
45}
46
47impl PriceFeed {
48    pub(crate) fn init(
49        &mut self,
50        bump: u8,
51        index: u16,
52        provider: PriceProviderKind,
53        store: &Pubkey,
54        authority: &Pubkey,
55        token: &Pubkey,
56        feed_id: &Pubkey,
57    ) -> Result<()> {
58        self.bump = bump;
59        self.provider = provider.into();
60        self.index = index;
61        self.store = *store;
62        self.authority = *authority;
63        self.token = *token;
64        self.feed_id = *feed_id;
65        Ok(())
66    }
67
68    pub(crate) fn update(&mut self, price: &PriceFeedPrice, max_future_excess: u64) -> Result<()> {
69        let clock = Clock::get()?;
70        let slot = clock.slot;
71        let current_ts = clock.unix_timestamp;
72
73        // Validate time.
74        require_gte!(
75            slot,
76            self.last_published_at_slot,
77            CoreError::PreconditionsAreNotMet
78        );
79        require_gte!(
80            current_ts,
81            self.last_published_at,
82            CoreError::PreconditionsAreNotMet
83        );
84
85        require_gte!(price.ts, self.price.ts, CoreError::InvalidArgument);
86        require_gte!(
87            current_ts.saturating_add_unsigned(max_future_excess),
88            price.ts,
89            CoreError::InvalidArgument
90        );
91        require_gte!(price.max_price, price.min_price, CoreError::InvalidArgument);
92        require_gte!(price.max_price, price.price, CoreError::InvalidArgument);
93        require_gte!(price.price, price.min_price, CoreError::InvalidArgument);
94
95        self.last_published_at_slot = slot;
96        self.last_published_at = current_ts;
97        self.price = *price;
98
99        Ok(())
100    }
101
102    /// Get provider.
103    pub fn provider(&self) -> Result<PriceProviderKind> {
104        PriceProviderKind::try_from(self.provider)
105            .map_err(|_| error!(CoreError::InvalidProviderKindIndex))
106    }
107
108    /// Get price feed price.
109    pub fn price(&self) -> &PriceFeedPrice {
110        &self.price
111    }
112
113    /// Get published slot.
114    pub fn last_published_at_slot(&self) -> u64 {
115        self.last_published_at_slot
116    }
117
118    /// Get feed id.
119    pub fn feed_id(&self) -> &Pubkey {
120        &self.feed_id
121    }
122
123    pub(crate) fn check_and_get_price(
124        &self,
125        clock: &Clock,
126        token_config: &TokenConfig,
127    ) -> Result<(u64, i64, gmsol_utils::Price)> {
128        let provider = self.provider()?;
129        require_eq!(
130            token_config.expected_provider().map_err(CoreError::from)?,
131            provider
132        );
133        let feed_id = token_config.get_feed(&provider).map_err(CoreError::from)?;
134
135        require_keys_eq!(self.feed_id, feed_id, CoreError::InvalidPriceFeedAccount);
136        require!(self.price.is_market_open(), CoreError::MarketNotOpen);
137
138        let timestamp = self.price.ts;
139        let current = clock.unix_timestamp;
140        if current > timestamp && current - timestamp > token_config.heartbeat_duration().into() {
141            return Err(CoreError::PriceFeedNotUpdated.into());
142        }
143
144        let price = self.try_to_price(token_config)?;
145
146        Ok((self.last_published_at_slot(), timestamp, price))
147    }
148
149    fn try_to_price(&self, token_config: &TokenConfig) -> Result<gmsol_utils::Price> {
150        self.price().try_to_price(token_config)
151    }
152}
153
154/// Price Feed Flags.
155#[repr(u8)]
156#[non_exhaustive]
157#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
158pub enum PriceFlag {
159    /// Is Market Opened.
160    Open,
161    // CHECK: should have no more than `MAX_FLAGS` of flags.
162}
163
164gmsol_utils::flags!(PriceFlag, MAX_FLAGS, u8);
165
166/// Price structure for Price Feed.
167#[zero_copy]
168#[cfg_attr(feature = "debug", derive(Debug))]
169pub struct PriceFeedPrice {
170    decimals: u8,
171    flags: PriceFlagContainer,
172    padding: [u8; 6],
173    ts: i64,
174    price: u128,
175    min_price: u128,
176    max_price: u128,
177}
178
179impl PriceFeedPrice {
180    /// Get ts.
181    pub fn ts(&self) -> i64 {
182        self.ts
183    }
184
185    /// Get min price.
186    pub fn min_price(&self) -> &u128 {
187        &self.min_price
188    }
189
190    /// Get max price.
191    pub fn max_price(&self) -> &u128 {
192        &self.max_price
193    }
194
195    /// Get price.
196    pub fn price(&self) -> &u128 {
197        &self.price
198    }
199
200    /// Is market open.
201    pub fn is_market_open(&self) -> bool {
202        self.flags.get_flag(PriceFlag::Open)
203    }
204
205    /// Try converting to [`Price`](gmsol_utils::Price).
206    pub fn try_to_price(&self, token_config: &TokenConfig) -> Result<gmsol_utils::Price> {
207        use gmsol_utils::price::{Decimal, Price};
208
209        let token_decimals = token_config.token_decimals();
210        let precision = token_config.precision();
211
212        let min = Decimal::try_from_price(self.min_price, self.decimals, token_decimals, precision)
213            .map_err(|_| error!(CoreError::InvalidPriceFeedPrice))?;
214
215        let max = Decimal::try_from_price(self.max_price, self.decimals, token_decimals, precision)
216            .map_err(|_| error!(CoreError::InvalidPriceFeedPrice))?;
217
218        Ok(Price { min, max })
219    }
220
221    /// Create from chainlink price report.
222    pub fn from_chainlink_report(
223        report: &gmsol_chainlink_datastreams::report::Report,
224    ) -> Result<Self> {
225        use gmsol_chainlink_datastreams::report::Report;
226        use gmsol_utils::price::{find_divisor_decimals, TEN, U192};
227
228        let price = report
229            .non_negative_price()
230            .ok_or_else(|| error!(CoreError::NegativePriceIsNotSupported))?;
231        let bid = report
232            .non_negative_bid()
233            .ok_or_else(|| error!(CoreError::NegativePriceIsNotSupported))?;
234        let ask = report
235            .non_negative_ask()
236            .ok_or_else(|| error!(CoreError::NegativePriceIsNotSupported))?;
237
238        require!(ask >= price, CoreError::InvalidPriceReport);
239        require!(price >= bid, CoreError::InvalidPriceReport);
240
241        let divisor_decimals = find_divisor_decimals(&ask);
242
243        require_gte!(Report::DECIMALS, divisor_decimals, CoreError::PriceOverflow);
244
245        let divisor = TEN.pow(U192::from(divisor_decimals));
246
247        debug_assert!(!divisor.is_zero());
248
249        let mut price = Self {
250            decimals: Report::DECIMALS - divisor_decimals,
251            flags: Default::default(),
252            padding: [0; 6],
253            ts: i64::from(report.observations_timestamp),
254            price: (price / divisor).try_into().unwrap(),
255            min_price: (bid / divisor).try_into().unwrap(),
256            max_price: (ask / divisor).try_into().unwrap(),
257        };
258
259        price.flags.set_flag(PriceFlag::Open, true);
260
261        Ok(price)
262    }
263}