1use solana_pubkey::Pubkey;
2
3use crate::state::AccountDataError;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct ReserveInfo {
12 pub address: Pubkey,
14 pub lending_market: Pubkey,
16 pub liquidity_mint: Pubkey,
18 pub liquidity_token_program: Pubkey,
20 pub pyth_oracle: Option<Pubkey>,
22 pub switchboard_price_oracle: Option<Pubkey>,
24 pub switchboard_twap_oracle: Option<Pubkey>,
26 pub scope_prices: Option<Pubkey>,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct ObligationInfo {
35 pub address: Pubkey,
37 pub deposit_reserves: Vec<Pubkey>,
39 pub borrow_reserves: Vec<Pubkey>,
41 pub referrer: Option<Pubkey>,
43}
44
45pub(super) fn non_default(key: Pubkey) -> Option<Pubkey> {
47 if key == Pubkey::default() {
48 None
49 } else {
50 Some(key)
51 }
52}
53
54impl ReserveInfo {
55 pub fn from_account_data(
60 address: Pubkey,
61 data: &[u8],
62 ) -> Result<Self, crate::state::AccountDataError> {
63 let reserve = crate::state::from_account_data::<crate::state::Reserve>(data)?;
64 Ok(Self::from_reserve(address, reserve))
65 }
66
67 pub fn from_reserve(address: Pubkey, reserve: &crate::state::Reserve) -> Self {
69 Self {
70 address,
71 lending_market: reserve.lending_market,
72 liquidity_mint: reserve.liquidity.mint_pubkey,
73 liquidity_token_program: reserve.liquidity.token_program,
74 pyth_oracle: non_default(reserve.config.token_info.pyth_configuration.price),
75 switchboard_price_oracle: non_default(
76 reserve
77 .config
78 .token_info
79 .switchboard_configuration
80 .price_aggregator,
81 ),
82 switchboard_twap_oracle: non_default(
83 reserve
84 .config
85 .token_info
86 .switchboard_configuration
87 .twap_aggregator,
88 ),
89 scope_prices: non_default(reserve.config.token_info.scope_configuration.price_feed),
90 }
91 }
92}
93
94impl ObligationInfo {
95 pub fn from_account_data(
97 address: Pubkey,
98 data: &[u8],
99 ) -> Result<Self, crate::state::AccountDataError> {
100 let obligation = crate::state::from_account_data::<crate::state::Obligation>(data)?;
101 Ok(Self::from_obligation(address, obligation))
102 }
103
104 pub fn from_obligation(address: Pubkey, obligation: &crate::state::Obligation) -> Self {
106 let deposit_reserves = obligation
107 .deposits
108 .iter()
109 .filter(|d| d.deposit_reserve != Pubkey::default())
110 .map(|d| d.deposit_reserve)
111 .collect();
112 let borrow_reserves = obligation
113 .borrows
114 .iter()
115 .filter(|b| b.borrow_reserve != Pubkey::default())
116 .map(|b| b.borrow_reserve)
117 .collect();
118 Self {
119 address,
120 deposit_reserves,
121 borrow_reserves,
122 referrer: non_default(obligation.referrer),
123 }
124 }
125}
126
127impl From<(Pubkey, &crate::state::Reserve)> for ReserveInfo {
128 fn from((address, reserve): (Pubkey, &crate::state::Reserve)) -> Self {
129 Self::from_reserve(address, reserve)
130 }
131}
132
133impl TryFrom<(Pubkey, &[u8])> for ReserveInfo {
134 type Error = AccountDataError;
135 fn try_from((address, data): (Pubkey, &[u8])) -> Result<Self, Self::Error> {
136 Self::from_account_data(address, data)
137 }
138}
139
140#[cfg(feature = "solana-account")]
141impl TryFrom<(Pubkey, &solana_account::Account)> for ReserveInfo {
142 type Error = AccountDataError;
143 fn try_from(
144 (address, account): (Pubkey, &solana_account::Account),
145 ) -> Result<Self, Self::Error> {
146 Self::from_account_data(address, &account.data)
147 }
148}
149
150impl From<(Pubkey, &crate::state::Obligation)> for ObligationInfo {
151 fn from((address, obligation): (Pubkey, &crate::state::Obligation)) -> Self {
152 Self::from_obligation(address, obligation)
153 }
154}
155
156impl TryFrom<(Pubkey, &[u8])> for ObligationInfo {
157 type Error = AccountDataError;
158 fn try_from((address, data): (Pubkey, &[u8])) -> Result<Self, Self::Error> {
159 Self::from_account_data(address, data)
160 }
161}
162
163#[cfg(feature = "solana-account")]
164impl TryFrom<(Pubkey, &solana_account::Account)> for ObligationInfo {
165 type Error = AccountDataError;
166 fn try_from(
167 (address, account): (Pubkey, &solana_account::Account),
168 ) -> Result<Self, Self::Error> {
169 Self::from_account_data(address, &account.data)
170 }
171}
172
173#[derive(Clone, Debug)]
175pub(crate) struct ReserveWithFarms {
176 pub info: ReserveInfo,
177 pub farm_collateral: Option<Pubkey>,
178 pub farm_debt: Option<Pubkey>,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq)]
183pub enum ObligationContextError {
184 ReserveNotFound(Pubkey),
186}
187
188impl core::fmt::Display for ObligationContextError {
189 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
190 match self {
191 Self::ReserveNotFound(pk) => write!(f, "reserve not found in context: {pk}"),
192 }
193 }
194}
195
196impl std::error::Error for ObligationContextError {}
197
198#[derive(Clone, Debug)]
219pub struct ObligationContext {
220 pub(crate) obligation: ObligationInfo,
221 pub(crate) lending_market: Pubkey,
222 pub(crate) reserves: Vec<ReserveWithFarms>,
223}
224
225impl ObligationContext {
226 pub fn new(
228 obligation_address: Pubkey,
229 obligation: &crate::state::Obligation,
230 reserves: &[(Pubkey, &crate::state::Reserve)],
231 ) -> Self {
232 let obligation_info = ObligationInfo::from_obligation(obligation_address, obligation);
233 let lending_market = obligation.lending_market;
234 let reserves = reserves
235 .iter()
236 .map(|(addr, reserve)| ReserveWithFarms {
237 info: ReserveInfo::from_reserve(*addr, reserve),
238 farm_collateral: non_default(reserve.farm_collateral),
239 farm_debt: non_default(reserve.farm_debt),
240 })
241 .collect();
242
243 Self {
244 obligation: obligation_info,
245 lending_market,
246 reserves,
247 }
248 }
249
250 pub fn reserve_addresses_for_obligation(
259 obligation_data: &[u8],
260 ) -> Result<Vec<Pubkey>, crate::state::AccountDataError> {
261 let obligation =
262 crate::state::from_account_data::<crate::state::Obligation>(obligation_data)?;
263 let mut addrs: Vec<Pubkey> = obligation
264 .deposits
265 .iter()
266 .filter(|d| d.deposit_reserve != Pubkey::default())
267 .map(|d| d.deposit_reserve)
268 .chain(
269 obligation
270 .borrows
271 .iter()
272 .filter(|b| b.borrow_reserve != Pubkey::default())
273 .map(|b| b.borrow_reserve),
274 )
275 .collect();
276 addrs.sort_unstable();
277 addrs.dedup();
278 Ok(addrs)
279 }
280
281 pub fn from_account_data(
287 obligation_address: Pubkey,
288 obligation_data: &[u8],
289 reserves: &[(Pubkey, &[u8])],
290 ) -> Result<Self, crate::state::AccountDataError> {
291 let obligation =
292 crate::state::from_account_data::<crate::state::Obligation>(obligation_data)?;
293 let parsed_reserves: Result<Vec<_>, _> = reserves
294 .iter()
295 .map(|(addr, data)| {
296 let r = crate::state::from_account_data::<crate::state::Reserve>(data)?;
297 Ok((*addr, r))
298 })
299 .collect();
300 let parsed_reserves = parsed_reserves?;
301 let reserve_pairs: Vec<(Pubkey, &crate::state::Reserve)> =
302 parsed_reserves.iter().map(|(a, r)| (*a, *r)).collect();
303 Ok(Self::new(obligation_address, obligation, &reserve_pairs))
304 }
305
306 pub fn from_infos(
311 lending_market: Pubkey,
312 obligation: ObligationInfo,
313 reserves: &[ReserveInfo],
314 ) -> Self {
315 Self {
316 obligation,
317 lending_market,
318 reserves: reserves
319 .iter()
320 .map(|info| ReserveWithFarms {
321 info: info.clone(),
322 farm_collateral: None,
323 farm_debt: None,
324 })
325 .collect(),
326 }
327 }
328
329 pub fn obligation(&self) -> &ObligationInfo {
331 &self.obligation
332 }
333
334 pub fn reserve_info(&self, address: &Pubkey) -> Option<&ReserveInfo> {
336 self.reserves
337 .iter()
338 .find(|r| r.info.address == *address)
339 .map(|r| &r.info)
340 }
341
342 pub(crate) fn find_reserve(&self, address: &Pubkey) -> Option<&ReserveWithFarms> {
343 self.reserves.iter().find(|r| r.info.address == *address)
344 }
345
346 pub(crate) fn all_reserve_infos(&self) -> Vec<ReserveInfo> {
347 self.reserves.iter().map(|r| r.info.clone()).collect()
348 }
349
350 pub(crate) fn collateral_farms(&self, reserve_address: &Pubkey) -> Option<FarmsAccounts> {
351 let r = self.find_reserve(reserve_address)?;
352 let farm_state = r.farm_collateral?;
353 Some(FarmsAccounts {
354 reserve_farm_state: farm_state,
355 obligation_farm_user_state: crate::pda::farms_user_state(
356 &farm_state,
357 &self.obligation.address,
358 )
359 .0,
360 })
361 }
362
363 pub(crate) fn debt_farms(&self, reserve_address: &Pubkey) -> Option<FarmsAccounts> {
364 let r = self.find_reserve(reserve_address)?;
365 let farm_state = r.farm_debt?;
366 Some(FarmsAccounts {
367 reserve_farm_state: farm_state,
368 obligation_farm_user_state: crate::pda::farms_user_state(
369 &farm_state,
370 &self.obligation.address,
371 )
372 .0,
373 })
374 }
375}
376
377#[derive(Clone, Debug, PartialEq, Eq)]
379pub struct FarmsAccounts {
380 pub obligation_farm_user_state: Pubkey,
381 pub reserve_farm_state: Pubkey,
382}
383
384#[derive(Clone, Debug, PartialEq, Eq)]
387pub struct CallbackAccounts {
388 pub progress_callback_type: crate::types::ProgressCallbackType,
389 pub custom_account_0: Option<Pubkey>,
390 pub custom_account_1: Option<Pubkey>,
391}
392
393impl CallbackAccounts {
394 pub fn from_withdraw_ticket(ticket: &crate::state::WithdrawTicket) -> Option<Self> {
398 let cb_type = match ticket.progress_callback_type {
399 0 => return None,
400 1 => crate::types::ProgressCallbackType::KlendQueueAccountingHandlerOnKvault,
401 _ => return None,
402 };
403 Some(Self {
404 progress_callback_type: cb_type,
405 custom_account_0: non_default(ticket.progress_callback_custom_accounts[0]),
406 custom_account_1: non_default(ticket.progress_callback_custom_accounts[1]),
407 })
408 }
409
410 pub fn from_withdraw_ticket_data(
414 data: &[u8],
415 ) -> Result<Option<Self>, crate::state::AccountDataError> {
416 let ticket = crate::state::from_account_data::<crate::state::WithdrawTicket>(data)?;
417 Ok(Self::from_withdraw_ticket(ticket))
418 }
419
420 #[cfg(feature = "solana-account")]
424 pub fn from_withdraw_ticket_account(
425 account: &solana_account::Account,
426 ) -> Result<Option<Self>, crate::state::AccountDataError> {
427 Self::from_withdraw_ticket_data(&account.data)
428 }
429}