gmsol_sdk/utils/
token_map.rs

1use std::{
2    collections::HashMap,
3    fmt,
4    iter::{Peekable, Zip},
5    slice::Iter,
6    sync::Arc,
7};
8
9use bytes::Bytes;
10use gmsol_programs::{
11    anchor_lang::{self, AccountDeserialize},
12    gmsol_store::accounts::TokenMapHeader,
13};
14use gmsol_utils::{
15    dynamic_access,
16    token_config::{TokenConfig, TokenMapAccess},
17};
18use solana_sdk::pubkey::Pubkey;
19
20use gmsol_utils::{oracle::PriceProviderKind, token_config::TokensWithFeed};
21use solana_sdk::instruction::AccountMeta;
22
23use crate::utils::zero_copy::{check_discriminator, try_deserialize_unchecked};
24
25/// Token Map.
26#[derive(Debug, Clone)]
27pub struct TokenMap {
28    header: Arc<TokenMapHeader>,
29    configs: Bytes,
30}
31
32impl TokenMapAccess for TokenMap {
33    fn get(&self, token: &Pubkey) -> Option<&TokenConfig> {
34        let index = usize::from(*self.header.tokens.get(token)?);
35        dynamic_access::get(&self.configs, index)
36    }
37}
38
39impl TokenMap {
40    /// Get the header.
41    pub fn header(&self) -> &TokenMapHeader {
42        &self.header
43    }
44
45    /// Is empty.
46    pub fn is_empty(&self) -> bool {
47        self.header.tokens.is_empty()
48    }
49
50    /// Get the number of tokens in the map.
51    pub fn len(&self) -> usize {
52        self.header.tokens.len()
53    }
54
55    /// Get all tokens.
56    pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
57        self.header
58            .tokens
59            .entries()
60            .map(|(k, _)| Pubkey::new_from_array(*k))
61    }
62
63    /// Create an iterator over the entires of the map.
64    pub fn iter(&self) -> impl Iterator<Item = (Pubkey, &TokenConfig)> + '_ {
65        self.tokens()
66            .filter_map(|token| self.get(&token).map(|config| (token, config)))
67    }
68}
69
70impl AccountDeserialize for TokenMap {
71    fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
72        check_discriminator::<TokenMapHeader>(buf)?;
73        Self::try_deserialize_unchecked(buf)
74    }
75
76    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
77        let header = Arc::new(try_deserialize_unchecked::<TokenMapHeader>(buf)?);
78        let (_disc, data) = buf.split_at(8);
79        let (_header, configs) = data.split_at(std::mem::size_of::<TokenMapHeader>());
80        Ok(Self {
81            header,
82            configs: Bytes::copy_from_slice(configs),
83        })
84    }
85}
86
87type Parser = Arc<dyn Fn(Pubkey) -> crate::Result<AccountMeta>>;
88
89/// A mapping from feed id to the corresponding feed address.
90pub type FeedAddressMap = std::collections::HashMap<Pubkey, Pubkey>;
91
92/// Feeds parser.
93#[derive(Default, Clone)]
94pub struct FeedsParser {
95    parsers: HashMap<PriceProviderKind, Parser>,
96}
97
98impl fmt::Debug for FeedsParser {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.debug_struct("FeedsParser").finish_non_exhaustive()
101    }
102}
103
104impl FeedsParser {
105    /// Parse a [`TokensWithFeed`]
106    pub fn parse<'a>(
107        &'a self,
108        tokens_with_feed: &'a TokensWithFeed,
109    ) -> impl Iterator<Item = crate::Result<AccountMeta>> + 'a {
110        Feeds::new(tokens_with_feed).map(|res| {
111            res.and_then(|FeedConfig { provider, feed, .. }| self.dispatch(&provider, &feed))
112        })
113    }
114
115    /// Parse and sort by tokens.
116    pub fn parse_and_sort_by_tokens(
117        &self,
118        tokens_with_feed: &TokensWithFeed,
119    ) -> crate::Result<Vec<AccountMeta>> {
120        let accounts = self
121            .parse(tokens_with_feed)
122            .collect::<crate::Result<Vec<_>>>()?;
123
124        let mut combined = tokens_with_feed
125            .tokens
126            .iter()
127            .zip(accounts)
128            .collect::<Vec<_>>();
129
130        combined.sort_by_key(|(key, _)| *key);
131
132        Ok(combined.into_iter().map(|(_, account)| account).collect())
133    }
134
135    fn dispatch(&self, provider: &PriceProviderKind, feed: &Pubkey) -> crate::Result<AccountMeta> {
136        let Some(parser) = self.parsers.get(provider) else {
137            return Ok(AccountMeta {
138                pubkey: *feed,
139                is_signer: false,
140                is_writable: false,
141            });
142        };
143        (parser)(*feed)
144    }
145
146    /// Insert a pull oracle feed parser.
147    pub fn insert_pull_oracle_feed_parser(
148        &mut self,
149        provider: PriceProviderKind,
150        map: FeedAddressMap,
151    ) -> &mut Self {
152        self.parsers.insert(
153            provider,
154            Arc::new(move |feed_id| {
155                let price_update = map.get(&feed_id).ok_or_else(|| {
156                    crate::Error::custom(format!("feed account for {feed_id} not provided"))
157                })?;
158
159                Ok(AccountMeta {
160                    pubkey: *price_update,
161                    is_signer: false,
162                    is_writable: false,
163                })
164            }),
165        );
166        self
167    }
168}
169
170/// Feed account metas.
171pub struct Feeds<'a> {
172    provider_with_lengths: Peekable<Zip<Iter<'a, u8>, Iter<'a, u16>>>,
173    tokens: Iter<'a, Pubkey>,
174    feeds: Iter<'a, Pubkey>,
175    current: usize,
176    failed: bool,
177}
178
179impl<'a> Feeds<'a> {
180    /// Create from [`TokensWithFeed`].
181    pub fn new(token_with_feeds: &'a TokensWithFeed) -> Self {
182        let providers = token_with_feeds.providers.iter();
183        let nums = token_with_feeds.nums.iter();
184        let provider_with_lengths = providers.zip(nums).peekable();
185        let tokens = token_with_feeds.tokens.iter();
186        let feeds = token_with_feeds.feeds.iter();
187        Self {
188            provider_with_lengths,
189            tokens,
190            feeds,
191            current: 0,
192            failed: false,
193        }
194    }
195}
196
197impl Iterator for Feeds<'_> {
198    type Item = crate::Result<FeedConfig>;
199
200    fn next(&mut self) -> Option<Self::Item> {
201        if self.failed {
202            return None;
203        }
204        loop {
205            let (provider, length) = self.provider_with_lengths.peek()?;
206            if self.current == (**length as usize) {
207                self.provider_with_lengths.next();
208                self.current = 0;
209                continue;
210            }
211            let Ok(provider) = PriceProviderKind::try_from(**provider) else {
212                self.failed = true;
213                return Some(Err(crate::Error::custom("invalid provider index")));
214            };
215            let Some(feed) = self.feeds.next() else {
216                return Some(Err(crate::Error::custom("not enough feeds")));
217            };
218            let Some(token) = self.tokens.next() else {
219                return Some(Err(crate::Error::custom("not enough tokens")));
220            };
221            self.current += 1;
222            return Some(Ok(FeedConfig {
223                token: *token,
224                provider,
225                feed: *feed,
226            }));
227        }
228    }
229}
230
231/// A feed config.
232#[derive(Debug, Clone)]
233pub struct FeedConfig {
234    /// Token.
235    pub token: Pubkey,
236    /// Provider Kind.
237    pub provider: PriceProviderKind,
238    /// Feed.
239    pub feed: Pubkey,
240}