drift_rs/math/
account_list_builder.rs

1use ahash::{HashMap, HashMapExt};
2use arrayvec::ArrayVec;
3use solana_sdk::{account::Account, pubkey::Pubkey};
4
5use crate::{
6    accounts::State,
7    constants::{self, oracle_source_to_owner, state_account},
8    ffi::{AccountWithKey, AccountsList},
9    types::accounts::User,
10    utils::zero_account_to_bytes,
11    DriftClient, MarketId, SdkError, SdkResult,
12};
13
14/// Builds a list of users's associated spot, perp, and oracle accounts
15///
16/// ```example(no_run)
17/// let mut builder = AccountsListBuilder::default();
18/// let accounts_list = builder.try_build(client, user).expect("build accounts");
19/// ```
20#[derive(Default)]
21pub struct AccountsListBuilder {
22    /// placeholder account values populated with real market & oracle account data
23    perp_accounts: ArrayVec<AccountWithKey, 16>,
24    spot_accounts: ArrayVec<AccountWithKey, 16>,
25    oracle_accounts: ArrayVec<AccountWithKey, 16>,
26}
27
28impl AccountsListBuilder {
29    /// Constructs an accounts list from `user` positions sync
30    ///
31    /// * `client` - drift client instance
32    /// * `user` - the account to build against
33    /// * `force_markets` - additional market accounts that should be included in the account list
34    ///
35    /// It relies on the `client` being subscribed to all the necessary markets and oracles
36    pub fn try_build(
37        &mut self,
38        client: &DriftClient,
39        user: &User,
40        force_markets: &[MarketId],
41    ) -> SdkResult<AccountsList> {
42        let mut oracle_markets = HashMap::<Pubkey, MarketId>::with_capacity(16);
43        let drift_state_account = client.try_get_account::<State>(state_account())?;
44
45        let force_spot_iter = force_markets
46            .iter()
47            .filter(|m| m.is_spot())
48            .map(|m| m.index());
49
50        let spot_market_idxs = ahash::HashSet::from_iter(
51            user.spot_positions
52                .iter()
53                .filter(|p| !p.is_available())
54                .map(|p| p.market_index)
55                .chain(force_spot_iter)
56                .chain(std::iter::once(MarketId::QUOTE_SPOT.index())),
57        );
58
59        for idx in spot_market_idxs {
60            let market = client.try_get_spot_market_account(idx)?;
61            oracle_markets.insert(market.oracle, MarketId::spot(market.market_index));
62            self.spot_accounts.push(
63                (
64                    market.pubkey,
65                    Account {
66                        data: zero_account_to_bytes(market),
67                        owner: constants::PROGRAM_ID,
68                        ..Default::default()
69                    },
70                )
71                    .into(),
72            );
73        }
74
75        let force_perp_iter = force_markets
76            .iter()
77            .filter(|m| m.is_perp())
78            .map(|m| m.index());
79        let perp_market_idxs = ahash::HashSet::from_iter(
80            user.perp_positions
81                .iter()
82                .filter(|p| !p.is_available())
83                .map(|p| p.market_index)
84                .chain(force_perp_iter),
85        );
86
87        for idx in perp_market_idxs {
88            let market = client.try_get_perp_market_account(idx)?;
89            oracle_markets.insert(market.amm.oracle, MarketId::perp(market.market_index));
90            self.perp_accounts.push(
91                (
92                    market.pubkey,
93                    Account {
94                        data: zero_account_to_bytes(market),
95                        owner: constants::PROGRAM_ID,
96                        ..Default::default()
97                    },
98                )
99                    .into(),
100            );
101        }
102
103        let mut latest_oracle_slot = 0;
104        for (oracle_key, market) in oracle_markets.iter() {
105            let oracle = client
106                .try_get_oracle_price_data_and_slot(*market)
107                .ok_or(SdkError::NoMarketData(*market))?;
108
109            latest_oracle_slot = oracle.slot.max(latest_oracle_slot);
110            let oracle_owner = oracle_source_to_owner(client.context, oracle.source);
111            self.oracle_accounts.push(
112                (
113                    *oracle_key,
114                    Account {
115                        data: oracle.raw,
116                        owner: oracle_owner,
117                        ..Default::default()
118                    },
119                )
120                    .into(),
121            );
122        }
123
124        Ok(AccountsList {
125            perp_markets: self.perp_accounts.as_mut_slice(),
126            spot_markets: self.spot_accounts.as_mut_slice(),
127            oracles: self.oracle_accounts.as_mut_slice(),
128            oracle_guard_rails: Some(drift_state_account.oracle_guard_rails),
129            latest_slot: latest_oracle_slot,
130        })
131    }
132
133    /// Constructs an accounts list from `user` positions
134    /// fetching from RPC as necessary
135    ///
136    /// * `client` - drift client instance
137    /// * `user` - the account to build against
138    /// * `force_markets` - additional market accounts that should be included in the account list
139    ///
140    /// like `try_build` but will fall back to network queries to fetch market/oracle accounts as required
141    /// if the client is already subscribed to necessary market/oracles then no network requests are made.
142    pub async fn build(
143        &mut self,
144        client: &DriftClient,
145        user: &User,
146        force_markets: &[MarketId],
147    ) -> SdkResult<AccountsList> {
148        let mut oracle_markets = HashMap::<Pubkey, MarketId>::with_capacity(16);
149        let drift_state_account = client.try_get_account::<State>(state_account())?;
150
151        // TODO: could batch the requests
152        let force_spot_iter = force_markets
153            .iter()
154            .filter(|m| m.is_spot())
155            .map(|m| m.index());
156        let spot_market_idxs = ahash::HashSet::from_iter(
157            user.spot_positions
158                .iter()
159                .filter(|p| !p.is_available())
160                .map(|p| p.market_index)
161                .chain(force_spot_iter)
162                .chain(std::iter::once(MarketId::QUOTE_SPOT.index())),
163        );
164
165        for market_idx in spot_market_idxs.iter() {
166            let market = client.get_spot_market_account(*market_idx).await?;
167            oracle_markets.insert(market.oracle, MarketId::spot(market.market_index));
168
169            self.spot_accounts.push(
170                (
171                    market.pubkey,
172                    Account {
173                        data: zero_account_to_bytes(market),
174                        owner: constants::PROGRAM_ID,
175                        ..Default::default()
176                    },
177                )
178                    .into(),
179            );
180        }
181
182        let force_perp_iter = force_markets
183            .iter()
184            .filter(|m| m.is_perp())
185            .map(|m| m.index());
186        let perp_market_idxs = ahash::HashSet::from_iter(
187            user.perp_positions
188                .iter()
189                .filter(|p| !p.is_available())
190                .map(|p| p.market_index)
191                .chain(force_perp_iter),
192        );
193
194        for market_idx in perp_market_idxs.iter() {
195            let market = client.get_perp_market_account(*market_idx).await?;
196            oracle_markets.insert(market.amm.oracle, MarketId::perp(market.market_index));
197
198            self.perp_accounts.push(
199                (
200                    market.pubkey,
201                    Account {
202                        data: zero_account_to_bytes(market),
203                        owner: constants::PROGRAM_ID,
204                        ..Default::default()
205                    },
206                )
207                    .into(),
208            );
209        }
210
211        let mut latest_oracle_slot = 0;
212        for (oracle_key, market) in oracle_markets.iter() {
213            let oracle = client.get_oracle_price_data_and_slot(*market).await?;
214
215            latest_oracle_slot = oracle.slot.max(latest_oracle_slot);
216            let oracle_owner = oracle_source_to_owner(client.context, oracle.source);
217            self.oracle_accounts.push(
218                (
219                    *oracle_key,
220                    Account {
221                        data: oracle.raw,
222                        owner: oracle_owner,
223                        ..Default::default()
224                    },
225                )
226                    .into(),
227            );
228        }
229
230        Ok(AccountsList {
231            perp_markets: self.perp_accounts.as_mut_slice(),
232            spot_markets: self.spot_accounts.as_mut_slice(),
233            oracles: self.oracle_accounts.as_mut_slice(),
234            oracle_guard_rails: Some(drift_state_account.oracle_guard_rails),
235            latest_slot: latest_oracle_slot,
236        })
237    }
238}