gmsol_store/states/oracle/
feed.rs1use 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#[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 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 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 pub fn provider(&self) -> Result<PriceProviderKind> {
104 PriceProviderKind::try_from(self.provider)
105 .map_err(|_| error!(CoreError::InvalidProviderKindIndex))
106 }
107
108 pub fn price(&self) -> &PriceFeedPrice {
110 &self.price
111 }
112
113 pub fn last_published_at_slot(&self) -> u64 {
115 self.last_published_at_slot
116 }
117
118 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#[repr(u8)]
156#[non_exhaustive]
157#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
158pub enum PriceFlag {
159 Open,
161 }
163
164gmsol_utils::flags!(PriceFlag, MAX_FLAGS, u8);
165
166#[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 pub fn ts(&self) -> i64 {
182 self.ts
183 }
184
185 pub fn min_price(&self) -> &u128 {
187 &self.min_price
188 }
189
190 pub fn max_price(&self) -> &u128 {
192 &self.max_price
193 }
194
195 pub fn price(&self) -> &u128 {
197 &self.price
198 }
199
200 pub fn is_market_open(&self) -> bool {
202 self.flags.get_flag(PriceFlag::Open)
203 }
204
205 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 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}