spl_token_lending/
processor.rs

1//! Program state processor
2
3use crate::{
4    error::LendingError,
5    instruction::LendingInstruction,
6    math::{Decimal, Rate, TryAdd, TryDiv, TryMul, WAD},
7    pyth,
8    state::{
9        CalculateBorrowResult, CalculateLiquidationResult, CalculateRepayResult,
10        InitLendingMarketParams, InitObligationParams, InitReserveParams, LendingMarket,
11        NewReserveCollateralParams, NewReserveLiquidityParams, Obligation, Reserve,
12        ReserveCollateral, ReserveConfig, ReserveLiquidity,
13    },
14};
15use num_traits::FromPrimitive;
16use solana_program::{
17    account_info::{next_account_info, AccountInfo},
18    decode_error::DecodeError,
19    entrypoint::ProgramResult,
20    instruction::Instruction,
21    msg,
22    program::{invoke, invoke_signed},
23    program_error::{PrintProgramError, ProgramError},
24    program_pack::{IsInitialized, Pack},
25    pubkey::Pubkey,
26    sysvar::{clock::Clock, rent::Rent, Sysvar},
27};
28use spl_token::solana_program::instruction::AccountMeta;
29use spl_token::state::{Account, Mint};
30use std::convert::TryInto;
31
32/// Processes an instruction
33pub fn process_instruction(
34    program_id: &Pubkey,
35    accounts: &[AccountInfo],
36    input: &[u8],
37) -> ProgramResult {
38    let instruction = LendingInstruction::unpack(input)?;
39    match instruction {
40        LendingInstruction::InitLendingMarket {
41            owner,
42            quote_currency,
43        } => {
44            msg!("Instruction: Init Lending Market");
45            process_init_lending_market(program_id, owner, quote_currency, accounts)
46        }
47        LendingInstruction::SetLendingMarketOwner { new_owner } => {
48            msg!("Instruction: Set Lending Market Owner");
49            process_set_lending_market_owner(program_id, new_owner, accounts)
50        }
51        LendingInstruction::InitReserve {
52            liquidity_amount,
53            config,
54        } => {
55            msg!("Instruction: Init Reserve");
56            process_init_reserve(program_id, liquidity_amount, config, accounts)
57        }
58        LendingInstruction::RefreshReserve => {
59            msg!("Instruction: Refresh Reserve");
60            process_refresh_reserve(program_id, accounts)
61        }
62        LendingInstruction::DepositReserveLiquidity { liquidity_amount } => {
63            msg!("Instruction: Deposit Reserve Liquidity");
64            process_deposit_reserve_liquidity(program_id, liquidity_amount, accounts)
65        }
66        LendingInstruction::RedeemReserveCollateral { collateral_amount } => {
67            msg!("Instruction: Redeem Reserve Collateral");
68            process_redeem_reserve_collateral(program_id, collateral_amount, accounts)
69        }
70        LendingInstruction::InitObligation => {
71            msg!("Instruction: Init Obligation");
72            process_init_obligation(program_id, accounts)
73        }
74        LendingInstruction::RefreshObligation => {
75            msg!("Instruction: Refresh Obligation");
76            process_refresh_obligation(program_id, accounts)
77        }
78        LendingInstruction::DepositObligationCollateral { collateral_amount } => {
79            msg!("Instruction: Deposit Obligation Collateral");
80            process_deposit_obligation_collateral(program_id, collateral_amount, accounts)
81        }
82        LendingInstruction::WithdrawObligationCollateral { collateral_amount } => {
83            msg!("Instruction: Withdraw Obligation Collateral");
84            process_withdraw_obligation_collateral(program_id, collateral_amount, accounts)
85        }
86        LendingInstruction::BorrowObligationLiquidity { liquidity_amount } => {
87            msg!("Instruction: Borrow Obligation Liquidity");
88            process_borrow_obligation_liquidity(program_id, liquidity_amount, accounts)
89        }
90        LendingInstruction::RepayObligationLiquidity { liquidity_amount } => {
91            msg!("Instruction: Repay Obligation Liquidity");
92            process_repay_obligation_liquidity(program_id, liquidity_amount, accounts)
93        }
94        LendingInstruction::LiquidateObligation { liquidity_amount } => {
95            msg!("Instruction: Liquidate Obligation");
96            process_liquidate_obligation(program_id, liquidity_amount, accounts)
97        }
98        LendingInstruction::FlashLoan { amount } => {
99            msg!("Instruction: Flash Loan");
100            process_flash_loan(program_id, amount, accounts)
101        }
102    }
103}
104
105fn process_init_lending_market(
106    program_id: &Pubkey,
107    owner: Pubkey,
108    quote_currency: [u8; 32],
109    accounts: &[AccountInfo],
110) -> ProgramResult {
111    let account_info_iter = &mut accounts.iter();
112    let lending_market_info = next_account_info(account_info_iter)?;
113    let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
114    let token_program_id = next_account_info(account_info_iter)?;
115    let oracle_program_id = next_account_info(account_info_iter)?;
116
117    assert_rent_exempt(rent, lending_market_info)?;
118    let mut lending_market = assert_uninitialized::<LendingMarket>(lending_market_info)?;
119    if lending_market_info.owner != program_id {
120        msg!("Lending market provided is not owned by the lending program");
121        return Err(LendingError::InvalidAccountOwner.into());
122    }
123
124    lending_market.init(InitLendingMarketParams {
125        bump_seed: Pubkey::find_program_address(&[lending_market_info.key.as_ref()], program_id).1,
126        owner,
127        quote_currency,
128        token_program_id: *token_program_id.key,
129        oracle_program_id: *oracle_program_id.key,
130    });
131    LendingMarket::pack(lending_market, &mut lending_market_info.data.borrow_mut())?;
132
133    Ok(())
134}
135
136#[inline(never)] // avoid stack frame limit
137fn process_set_lending_market_owner(
138    program_id: &Pubkey,
139    new_owner: Pubkey,
140    accounts: &[AccountInfo],
141) -> ProgramResult {
142    let account_info_iter = &mut accounts.iter();
143    let lending_market_info = next_account_info(account_info_iter)?;
144    let lending_market_owner_info = next_account_info(account_info_iter)?;
145
146    let mut lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
147    if lending_market_info.owner != program_id {
148        msg!("Lending market provided is not owned by the lending program");
149        return Err(LendingError::InvalidAccountOwner.into());
150    }
151    if &lending_market.owner != lending_market_owner_info.key {
152        msg!("Lending market owner does not match the lending market owner provided");
153        return Err(LendingError::InvalidMarketOwner.into());
154    }
155    if !lending_market_owner_info.is_signer {
156        msg!("Lending market owner provided must be a signer");
157        return Err(LendingError::InvalidSigner.into());
158    }
159
160    lending_market.owner = new_owner;
161    LendingMarket::pack(lending_market, &mut lending_market_info.data.borrow_mut())?;
162
163    Ok(())
164}
165
166fn process_init_reserve(
167    program_id: &Pubkey,
168    liquidity_amount: u64,
169    config: ReserveConfig,
170    accounts: &[AccountInfo],
171) -> ProgramResult {
172    if liquidity_amount == 0 {
173        msg!("Reserve must be initialized with liquidity");
174        return Err(LendingError::InvalidAmount.into());
175    }
176    if config.optimal_utilization_rate > 100 {
177        msg!("Optimal utilization rate must be in range [0, 100]");
178        return Err(LendingError::InvalidConfig.into());
179    }
180    if config.loan_to_value_ratio >= 100 {
181        msg!("Loan to value ratio must be in range [0, 100)");
182        return Err(LendingError::InvalidConfig.into());
183    }
184    if config.liquidation_bonus > 100 {
185        msg!("Liquidation bonus must be in range [0, 100]");
186        return Err(LendingError::InvalidConfig.into());
187    }
188    if config.liquidation_threshold <= config.loan_to_value_ratio
189        || config.liquidation_threshold > 100
190    {
191        msg!("Liquidation threshold must be in range (LTV, 100]");
192        return Err(LendingError::InvalidConfig.into());
193    }
194    if config.optimal_borrow_rate < config.min_borrow_rate {
195        msg!("Optimal borrow rate must be >= min borrow rate");
196        return Err(LendingError::InvalidConfig.into());
197    }
198    if config.optimal_borrow_rate > config.max_borrow_rate {
199        msg!("Optimal borrow rate must be <= max borrow rate");
200        return Err(LendingError::InvalidConfig.into());
201    }
202    if config.fees.borrow_fee_wad >= WAD {
203        msg!("Borrow fee must be in range [0, 1_000_000_000_000_000_000)");
204        return Err(LendingError::InvalidConfig.into());
205    }
206    if config.fees.flash_loan_fee_wad >= WAD {
207        msg!("Flash loan fee must be in range [0, 1_000_000_000_000_000_000)");
208        return Err(LendingError::InvalidConfig.into());
209    }
210    if config.fees.host_fee_percentage > 100 {
211        msg!("Host fee percentage must be in range [0, 100]");
212        return Err(LendingError::InvalidConfig.into());
213    }
214
215    let account_info_iter = &mut accounts.iter().peekable();
216    let source_liquidity_info = next_account_info(account_info_iter)?;
217    let destination_collateral_info = next_account_info(account_info_iter)?;
218    let reserve_info = next_account_info(account_info_iter)?;
219    let reserve_liquidity_mint_info = next_account_info(account_info_iter)?;
220    let reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
221    let reserve_liquidity_fee_receiver_info = next_account_info(account_info_iter)?;
222    let reserve_collateral_mint_info = next_account_info(account_info_iter)?;
223    let reserve_collateral_supply_info = next_account_info(account_info_iter)?;
224    let pyth_product_info = next_account_info(account_info_iter)?;
225    let pyth_price_info = next_account_info(account_info_iter)?;
226    let lending_market_info = next_account_info(account_info_iter)?;
227    let lending_market_authority_info = next_account_info(account_info_iter)?;
228    let lending_market_owner_info = next_account_info(account_info_iter)?;
229    let user_transfer_authority_info = next_account_info(account_info_iter)?;
230    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
231    let rent_info = next_account_info(account_info_iter)?;
232    let rent = &Rent::from_account_info(rent_info)?;
233    let token_program_id = next_account_info(account_info_iter)?;
234
235    assert_rent_exempt(rent, reserve_info)?;
236    let mut reserve = assert_uninitialized::<Reserve>(reserve_info)?;
237    if reserve_info.owner != program_id {
238        msg!("Reserve provided is not owned by the lending program");
239        return Err(LendingError::InvalidAccountOwner.into());
240    }
241
242    if reserve_liquidity_supply_info.key == source_liquidity_info.key {
243        msg!("Reserve liquidity supply cannot be used as the source liquidity provided");
244        return Err(LendingError::InvalidAccountInput.into());
245    }
246
247    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
248    if lending_market_info.owner != program_id {
249        msg!("Lending market provided is not owned by the lending program");
250        return Err(LendingError::InvalidAccountOwner.into());
251    }
252    if &lending_market.token_program_id != token_program_id.key {
253        msg!("Lending market token program does not match the token program provided");
254        return Err(LendingError::InvalidTokenProgram.into());
255    }
256    if &lending_market.owner != lending_market_owner_info.key {
257        msg!("Lending market owner does not match the lending market owner provided");
258        return Err(LendingError::InvalidMarketOwner.into());
259    }
260    if !lending_market_owner_info.is_signer {
261        msg!("Lending market owner provided must be a signer");
262        return Err(LendingError::InvalidSigner.into());
263    }
264
265    if &lending_market.oracle_program_id != pyth_product_info.owner {
266        msg!("Pyth product account provided is not owned by the lending market oracle program");
267        return Err(LendingError::InvalidOracleConfig.into());
268    }
269    if &lending_market.oracle_program_id != pyth_price_info.owner {
270        msg!("Pyth price account provided is not owned by the lending market oracle program");
271        return Err(LendingError::InvalidOracleConfig.into());
272    }
273
274    let pyth_product_data = pyth_product_info.try_borrow_data()?;
275    let pyth_product = pyth::load::<pyth::Product>(&pyth_product_data)
276        .map_err(|_| ProgramError::InvalidAccountData)?;
277    if pyth_product.magic != pyth::MAGIC {
278        msg!("Pyth product account provided is not a valid Pyth account");
279        return Err(LendingError::InvalidOracleConfig.into());
280    }
281    if pyth_product.ver != pyth::VERSION_2 {
282        msg!("Pyth product account provided has a different version than expected");
283        return Err(LendingError::InvalidOracleConfig.into());
284    }
285    if pyth_product.atype != pyth::AccountType::Product as u32 {
286        msg!("Pyth product account provided is not a valid Pyth product account");
287        return Err(LendingError::InvalidOracleConfig.into());
288    }
289
290    let pyth_price_pubkey_bytes: &[u8; 32] = pyth_price_info
291        .key
292        .as_ref()
293        .try_into()
294        .map_err(|_| LendingError::InvalidAccountInput)?;
295    if &pyth_product.px_acc.val != pyth_price_pubkey_bytes {
296        msg!("Pyth product price account does not match the Pyth price provided");
297        return Err(LendingError::InvalidOracleConfig.into());
298    }
299
300    let quote_currency = get_pyth_product_quote_currency(pyth_product)?;
301    if lending_market.quote_currency != quote_currency {
302        msg!("Lending market quote currency does not match the oracle quote currency");
303        return Err(LendingError::InvalidOracleConfig.into());
304    }
305
306    let market_price = get_pyth_price(pyth_price_info, clock)?;
307
308    let authority_signer_seeds = &[
309        lending_market_info.key.as_ref(),
310        &[lending_market.bump_seed],
311    ];
312    let lending_market_authority_pubkey =
313        Pubkey::create_program_address(authority_signer_seeds, program_id)?;
314    if &lending_market_authority_pubkey != lending_market_authority_info.key {
315        msg!(
316            "Derived lending market authority does not match the lending market authority provided"
317        );
318        return Err(LendingError::InvalidMarketAuthority.into());
319    }
320
321    let reserve_liquidity_mint = unpack_mint(&reserve_liquidity_mint_info.data.borrow())?;
322    if reserve_liquidity_mint_info.owner != token_program_id.key {
323        msg!("Reserve liquidity mint is not owned by the token program provided");
324        return Err(LendingError::InvalidTokenOwner.into());
325    }
326
327    reserve.init(InitReserveParams {
328        current_slot: clock.slot,
329        lending_market: *lending_market_info.key,
330        liquidity: ReserveLiquidity::new(NewReserveLiquidityParams {
331            mint_pubkey: *reserve_liquidity_mint_info.key,
332            mint_decimals: reserve_liquidity_mint.decimals,
333            supply_pubkey: *reserve_liquidity_supply_info.key,
334            fee_receiver: *reserve_liquidity_fee_receiver_info.key,
335            oracle_pubkey: *pyth_price_info.key,
336            market_price,
337        }),
338        collateral: ReserveCollateral::new(NewReserveCollateralParams {
339            mint_pubkey: *reserve_collateral_mint_info.key,
340            supply_pubkey: *reserve_collateral_supply_info.key,
341        }),
342        config,
343    });
344
345    let collateral_amount = reserve.deposit_liquidity(liquidity_amount)?;
346    Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
347
348    spl_token_init_account(TokenInitializeAccountParams {
349        account: reserve_liquidity_supply_info.clone(),
350        mint: reserve_liquidity_mint_info.clone(),
351        owner: lending_market_authority_info.clone(),
352        rent: rent_info.clone(),
353        token_program: token_program_id.clone(),
354    })?;
355
356    spl_token_init_account(TokenInitializeAccountParams {
357        account: reserve_liquidity_fee_receiver_info.clone(),
358        mint: reserve_liquidity_mint_info.clone(),
359        owner: lending_market_authority_info.clone(),
360        rent: rent_info.clone(),
361        token_program: token_program_id.clone(),
362    })?;
363
364    spl_token_init_mint(TokenInitializeMintParams {
365        mint: reserve_collateral_mint_info.clone(),
366        authority: lending_market_authority_info.key,
367        rent: rent_info.clone(),
368        decimals: reserve_liquidity_mint.decimals,
369        token_program: token_program_id.clone(),
370    })?;
371
372    spl_token_init_account(TokenInitializeAccountParams {
373        account: reserve_collateral_supply_info.clone(),
374        mint: reserve_collateral_mint_info.clone(),
375        owner: lending_market_authority_info.clone(),
376        rent: rent_info.clone(),
377        token_program: token_program_id.clone(),
378    })?;
379
380    spl_token_init_account(TokenInitializeAccountParams {
381        account: destination_collateral_info.clone(),
382        mint: reserve_collateral_mint_info.clone(),
383        owner: user_transfer_authority_info.clone(),
384        rent: rent_info.clone(),
385        token_program: token_program_id.clone(),
386    })?;
387
388    spl_token_transfer(TokenTransferParams {
389        source: source_liquidity_info.clone(),
390        destination: reserve_liquidity_supply_info.clone(),
391        amount: liquidity_amount,
392        authority: user_transfer_authority_info.clone(),
393        authority_signer_seeds: &[],
394        token_program: token_program_id.clone(),
395    })?;
396
397    spl_token_mint_to(TokenMintToParams {
398        mint: reserve_collateral_mint_info.clone(),
399        destination: destination_collateral_info.clone(),
400        amount: collateral_amount,
401        authority: lending_market_authority_info.clone(),
402        authority_signer_seeds,
403        token_program: token_program_id.clone(),
404    })?;
405
406    Ok(())
407}
408
409fn process_refresh_reserve(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
410    let account_info_iter = &mut accounts.iter().peekable();
411    let reserve_info = next_account_info(account_info_iter)?;
412    let reserve_liquidity_oracle_info = next_account_info(account_info_iter)?;
413    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
414
415    let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
416    if reserve_info.owner != program_id {
417        msg!("Reserve provided is not owned by the lending program");
418        return Err(LendingError::InvalidAccountOwner.into());
419    }
420    if &reserve.liquidity.oracle_pubkey != reserve_liquidity_oracle_info.key {
421        msg!("Reserve liquidity oracle does not match the reserve liquidity oracle provided");
422        return Err(LendingError::InvalidAccountInput.into());
423    }
424
425    reserve.liquidity.market_price = get_pyth_price(reserve_liquidity_oracle_info, clock)?;
426
427    reserve.accrue_interest(clock.slot)?;
428    reserve.last_update.update_slot(clock.slot);
429    Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
430
431    Ok(())
432}
433
434fn process_deposit_reserve_liquidity(
435    program_id: &Pubkey,
436    liquidity_amount: u64,
437    accounts: &[AccountInfo],
438) -> ProgramResult {
439    if liquidity_amount == 0 {
440        msg!("Liquidity amount provided cannot be zero");
441        return Err(LendingError::InvalidAmount.into());
442    }
443
444    let account_info_iter = &mut accounts.iter();
445    let source_liquidity_info = next_account_info(account_info_iter)?;
446    let destination_collateral_info = next_account_info(account_info_iter)?;
447    let reserve_info = next_account_info(account_info_iter)?;
448    let reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
449    let reserve_collateral_mint_info = next_account_info(account_info_iter)?;
450    let lending_market_info = next_account_info(account_info_iter)?;
451    let lending_market_authority_info = next_account_info(account_info_iter)?;
452    let user_transfer_authority_info = next_account_info(account_info_iter)?;
453    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
454    let token_program_id = next_account_info(account_info_iter)?;
455
456    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
457    if lending_market_info.owner != program_id {
458        msg!("Lending market provided is not owned by the lending program");
459        return Err(LendingError::InvalidAccountOwner.into());
460    }
461    if &lending_market.token_program_id != token_program_id.key {
462        msg!("Lending market token program does not match the token program provided");
463        return Err(LendingError::InvalidTokenProgram.into());
464    }
465
466    let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
467    if reserve_info.owner != program_id {
468        msg!("Reserve provided is not owned by the lending program");
469        return Err(LendingError::InvalidAccountOwner.into());
470    }
471    if &reserve.lending_market != lending_market_info.key {
472        msg!("Reserve lending market does not match the lending market provided");
473        return Err(LendingError::InvalidAccountInput.into());
474    }
475    if &reserve.liquidity.supply_pubkey != reserve_liquidity_supply_info.key {
476        msg!("Reserve liquidity supply does not match the reserve liquidity supply provided");
477        return Err(LendingError::InvalidAccountInput.into());
478    }
479    if &reserve.collateral.mint_pubkey != reserve_collateral_mint_info.key {
480        msg!("Reserve collateral mint does not match the reserve collateral mint provided");
481        return Err(LendingError::InvalidAccountInput.into());
482    }
483    if &reserve.liquidity.supply_pubkey == source_liquidity_info.key {
484        msg!("Reserve liquidity supply cannot be used as the source liquidity provided");
485        return Err(LendingError::InvalidAccountInput.into());
486    }
487    if &reserve.collateral.supply_pubkey == destination_collateral_info.key {
488        msg!("Reserve collateral supply cannot be used as the destination collateral provided");
489        return Err(LendingError::InvalidAccountInput.into());
490    }
491    if reserve.last_update.is_stale(clock.slot)? {
492        msg!("Reserve is stale and must be refreshed in the current slot");
493        return Err(LendingError::ReserveStale.into());
494    }
495
496    let authority_signer_seeds = &[
497        lending_market_info.key.as_ref(),
498        &[lending_market.bump_seed],
499    ];
500    let lending_market_authority_pubkey =
501        Pubkey::create_program_address(authority_signer_seeds, program_id)?;
502    if &lending_market_authority_pubkey != lending_market_authority_info.key {
503        msg!(
504            "Derived lending market authority {} does not match the lending market authority provided {}",
505            &lending_market_authority_pubkey.to_string(),
506            &lending_market_authority_info.key.to_string(),
507        );
508        return Err(LendingError::InvalidMarketAuthority.into());
509    }
510
511    let collateral_amount = reserve.deposit_liquidity(liquidity_amount)?;
512    reserve.last_update.mark_stale();
513    Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
514
515    spl_token_transfer(TokenTransferParams {
516        source: source_liquidity_info.clone(),
517        destination: reserve_liquidity_supply_info.clone(),
518        amount: liquidity_amount,
519        authority: user_transfer_authority_info.clone(),
520        authority_signer_seeds: &[],
521        token_program: token_program_id.clone(),
522    })?;
523
524    spl_token_mint_to(TokenMintToParams {
525        mint: reserve_collateral_mint_info.clone(),
526        destination: destination_collateral_info.clone(),
527        amount: collateral_amount,
528        authority: lending_market_authority_info.clone(),
529        authority_signer_seeds,
530        token_program: token_program_id.clone(),
531    })?;
532
533    Ok(())
534}
535
536fn process_redeem_reserve_collateral(
537    program_id: &Pubkey,
538    collateral_amount: u64,
539    accounts: &[AccountInfo],
540) -> ProgramResult {
541    if collateral_amount == 0 {
542        msg!("Collateral amount provided cannot be zero");
543        return Err(LendingError::InvalidAmount.into());
544    }
545
546    let account_info_iter = &mut accounts.iter();
547    let source_collateral_info = next_account_info(account_info_iter)?;
548    let destination_liquidity_info = next_account_info(account_info_iter)?;
549    let reserve_info = next_account_info(account_info_iter)?;
550    let reserve_collateral_mint_info = next_account_info(account_info_iter)?;
551    let reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
552    let lending_market_info = next_account_info(account_info_iter)?;
553    let lending_market_authority_info = next_account_info(account_info_iter)?;
554    let user_transfer_authority_info = next_account_info(account_info_iter)?;
555    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
556    let token_program_id = next_account_info(account_info_iter)?;
557
558    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
559    if lending_market_info.owner != program_id {
560        msg!("Lending market provided is not owned by the lending program");
561        return Err(LendingError::InvalidAccountOwner.into());
562    }
563    if &lending_market.token_program_id != token_program_id.key {
564        msg!("Lending market token program does not match the token program provided");
565        return Err(LendingError::InvalidTokenProgram.into());
566    }
567
568    let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
569    if reserve_info.owner != program_id {
570        msg!("Reserve provided is not owned by the lending program");
571        return Err(LendingError::InvalidAccountOwner.into());
572    }
573    if &reserve.lending_market != lending_market_info.key {
574        msg!("Reserve lending market does not match the lending market provided");
575        return Err(LendingError::InvalidAccountInput.into());
576    }
577    if &reserve.collateral.mint_pubkey != reserve_collateral_mint_info.key {
578        msg!("Reserve collateral mint does not match the reserve collateral mint provided");
579        return Err(LendingError::InvalidAccountInput.into());
580    }
581    if &reserve.collateral.supply_pubkey == source_collateral_info.key {
582        msg!("Reserve collateral supply cannot be used as the source collateral provided");
583        return Err(LendingError::InvalidAccountInput.into());
584    }
585    if &reserve.liquidity.supply_pubkey != reserve_liquidity_supply_info.key {
586        msg!("Reserve liquidity supply does not match the reserve liquidity supply provided");
587        return Err(LendingError::InvalidAccountInput.into());
588    }
589    if &reserve.liquidity.supply_pubkey == destination_liquidity_info.key {
590        msg!("Reserve liquidity supply cannot be used as the destination liquidity provided");
591        return Err(LendingError::InvalidAccountInput.into());
592    }
593    if reserve.last_update.is_stale(clock.slot)? {
594        msg!("Reserve is stale and must be refreshed in the current slot");
595        return Err(LendingError::ReserveStale.into());
596    }
597
598    let authority_signer_seeds = &[
599        lending_market_info.key.as_ref(),
600        &[lending_market.bump_seed],
601    ];
602    let lending_market_authority_pubkey =
603        Pubkey::create_program_address(authority_signer_seeds, program_id)?;
604    if &lending_market_authority_pubkey != lending_market_authority_info.key {
605        msg!(
606            "Derived lending market authority does not match the lending market authority provided"
607        );
608        return Err(LendingError::InvalidMarketAuthority.into());
609    }
610
611    let liquidity_amount = reserve.redeem_collateral(collateral_amount)?;
612    reserve.last_update.mark_stale();
613    Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
614
615    spl_token_burn(TokenBurnParams {
616        mint: reserve_collateral_mint_info.clone(),
617        source: source_collateral_info.clone(),
618        amount: collateral_amount,
619        authority: user_transfer_authority_info.clone(),
620        authority_signer_seeds: &[],
621        token_program: token_program_id.clone(),
622    })?;
623
624    spl_token_transfer(TokenTransferParams {
625        source: reserve_liquidity_supply_info.clone(),
626        destination: destination_liquidity_info.clone(),
627        amount: liquidity_amount,
628        authority: lending_market_authority_info.clone(),
629        authority_signer_seeds,
630        token_program: token_program_id.clone(),
631    })?;
632
633    Ok(())
634}
635
636#[inline(never)] // avoid stack frame limit
637fn process_init_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
638    let account_info_iter = &mut accounts.iter();
639    let obligation_info = next_account_info(account_info_iter)?;
640    let lending_market_info = next_account_info(account_info_iter)?;
641    let obligation_owner_info = next_account_info(account_info_iter)?;
642    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
643    let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
644    let token_program_id = next_account_info(account_info_iter)?;
645
646    assert_rent_exempt(rent, obligation_info)?;
647    let mut obligation = assert_uninitialized::<Obligation>(obligation_info)?;
648    if obligation_info.owner != program_id {
649        msg!("Obligation provided is not owned by the lending program");
650        return Err(LendingError::InvalidAccountOwner.into());
651    }
652
653    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
654    if lending_market_info.owner != program_id {
655        msg!("Lending market provided is not owned by the lending program");
656        return Err(LendingError::InvalidAccountOwner.into());
657    }
658    if &lending_market.token_program_id != token_program_id.key {
659        msg!("Lending market token program does not match the token program provided");
660        return Err(LendingError::InvalidTokenProgram.into());
661    }
662
663    if !obligation_owner_info.is_signer {
664        msg!("Obligation owner provided must be a signer");
665        return Err(LendingError::InvalidSigner.into());
666    }
667
668    obligation.init(InitObligationParams {
669        current_slot: clock.slot,
670        lending_market: *lending_market_info.key,
671        owner: *obligation_owner_info.key,
672        deposits: vec![],
673        borrows: vec![],
674    });
675    Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
676
677    Ok(())
678}
679
680fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
681    let account_info_iter = &mut accounts.iter().peekable();
682    let obligation_info = next_account_info(account_info_iter)?;
683    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
684
685    let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
686    if obligation_info.owner != program_id {
687        msg!("Obligation provided is not owned by the lending program");
688        return Err(LendingError::InvalidAccountOwner.into());
689    }
690
691    let mut deposited_value = Decimal::zero();
692    let mut borrowed_value = Decimal::zero();
693    let mut allowed_borrow_value = Decimal::zero();
694    let mut unhealthy_borrow_value = Decimal::zero();
695
696    for (index, collateral) in obligation.deposits.iter_mut().enumerate() {
697        let deposit_reserve_info = next_account_info(account_info_iter)?;
698        if deposit_reserve_info.owner != program_id {
699            msg!(
700                "Deposit reserve provided for collateral {} is not owned by the lending program",
701                index
702            );
703            return Err(LendingError::InvalidAccountOwner.into());
704        }
705        if collateral.deposit_reserve != *deposit_reserve_info.key {
706            msg!(
707                "Deposit reserve of collateral {} does not match the deposit reserve provided",
708                index
709            );
710            return Err(LendingError::InvalidAccountInput.into());
711        }
712
713        let deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?;
714        if deposit_reserve.last_update.is_stale(clock.slot)? {
715            msg!(
716                "Deposit reserve provided for collateral {} is stale and must be refreshed in the current slot",
717                index
718            );
719            return Err(LendingError::ReserveStale.into());
720        }
721
722        // @TODO: add lookup table https://git.io/JOCYq
723        let decimals = 10u64
724            .checked_pow(deposit_reserve.liquidity.mint_decimals as u32)
725            .ok_or(LendingError::MathOverflow)?;
726
727        let market_value = deposit_reserve
728            .collateral_exchange_rate()?
729            .decimal_collateral_to_liquidity(collateral.deposited_amount.into())?
730            .try_mul(deposit_reserve.liquidity.market_price)?
731            .try_div(decimals)?;
732        collateral.market_value = market_value;
733
734        let loan_to_value_rate = Rate::from_percent(deposit_reserve.config.loan_to_value_ratio);
735        let liquidation_threshold_rate =
736            Rate::from_percent(deposit_reserve.config.liquidation_threshold);
737
738        deposited_value = deposited_value.try_add(market_value)?;
739        allowed_borrow_value =
740            allowed_borrow_value.try_add(market_value.try_mul(loan_to_value_rate)?)?;
741        unhealthy_borrow_value =
742            unhealthy_borrow_value.try_add(market_value.try_mul(liquidation_threshold_rate)?)?;
743    }
744
745    for (index, liquidity) in obligation.borrows.iter_mut().enumerate() {
746        let borrow_reserve_info = next_account_info(account_info_iter)?;
747        if borrow_reserve_info.owner != program_id {
748            msg!(
749                "Borrow reserve provided for liquidity {} is not owned by the lending program",
750                index
751            );
752            return Err(LendingError::InvalidAccountOwner.into());
753        }
754        if liquidity.borrow_reserve != *borrow_reserve_info.key {
755            msg!(
756                "Borrow reserve of liquidity {} does not match the borrow reserve provided",
757                index
758            );
759            return Err(LendingError::InvalidAccountInput.into());
760        }
761
762        let borrow_reserve = Reserve::unpack(&borrow_reserve_info.data.borrow())?;
763        if borrow_reserve.last_update.is_stale(clock.slot)? {
764            msg!(
765                "Borrow reserve provided for liquidity {} is stale and must be refreshed in the current slot",
766                index
767            );
768            return Err(LendingError::ReserveStale.into());
769        }
770
771        liquidity.accrue_interest(borrow_reserve.liquidity.cumulative_borrow_rate_wads)?;
772
773        // @TODO: add lookup table https://git.io/JOCYq
774        let decimals = 10u64
775            .checked_pow(borrow_reserve.liquidity.mint_decimals as u32)
776            .ok_or(LendingError::MathOverflow)?;
777
778        let market_value = liquidity
779            .borrowed_amount_wads
780            .try_mul(borrow_reserve.liquidity.market_price)?
781            .try_div(decimals)?;
782        liquidity.market_value = market_value;
783
784        borrowed_value = borrowed_value.try_add(market_value)?;
785    }
786
787    if account_info_iter.peek().is_some() {
788        msg!("Too many obligation deposit or borrow reserves provided");
789        return Err(LendingError::InvalidAccountInput.into());
790    }
791
792    obligation.deposited_value = deposited_value;
793    obligation.borrowed_value = borrowed_value;
794    obligation.allowed_borrow_value = allowed_borrow_value;
795    obligation.unhealthy_borrow_value = unhealthy_borrow_value;
796
797    obligation.last_update.update_slot(clock.slot);
798    Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
799
800    Ok(())
801}
802
803#[inline(never)] // avoid stack frame limit
804fn process_deposit_obligation_collateral(
805    program_id: &Pubkey,
806    collateral_amount: u64,
807    accounts: &[AccountInfo],
808) -> ProgramResult {
809    if collateral_amount == 0 {
810        msg!("Collateral amount provided cannot be zero");
811        return Err(LendingError::InvalidAmount.into());
812    }
813
814    let account_info_iter = &mut accounts.iter();
815    let source_collateral_info = next_account_info(account_info_iter)?;
816    let destination_collateral_info = next_account_info(account_info_iter)?;
817    let deposit_reserve_info = next_account_info(account_info_iter)?;
818    let obligation_info = next_account_info(account_info_iter)?;
819    let lending_market_info = next_account_info(account_info_iter)?;
820    let obligation_owner_info = next_account_info(account_info_iter)?;
821    let user_transfer_authority_info = next_account_info(account_info_iter)?;
822    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
823    let token_program_id = next_account_info(account_info_iter)?;
824
825    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
826    if lending_market_info.owner != program_id {
827        msg!("Lending market provided is not owned by the lending program");
828        return Err(LendingError::InvalidAccountOwner.into());
829    }
830    if &lending_market.token_program_id != token_program_id.key {
831        msg!("Lending market token program does not match the token program provided");
832        return Err(LendingError::InvalidTokenProgram.into());
833    }
834
835    let deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?;
836    if deposit_reserve_info.owner != program_id {
837        msg!("Deposit reserve provided is not owned by the lending program");
838        return Err(LendingError::InvalidAccountOwner.into());
839    }
840    if &deposit_reserve.lending_market != lending_market_info.key {
841        msg!("Deposit reserve lending market does not match the lending market provided");
842        return Err(LendingError::InvalidAccountInput.into());
843    }
844    if &deposit_reserve.collateral.supply_pubkey == source_collateral_info.key {
845        msg!("Deposit reserve collateral supply cannot be used as the source collateral provided");
846        return Err(LendingError::InvalidAccountInput.into());
847    }
848    if &deposit_reserve.collateral.supply_pubkey != destination_collateral_info.key {
849        msg!(
850            "Deposit reserve collateral supply must be used as the destination collateral provided"
851        );
852        return Err(LendingError::InvalidAccountInput.into());
853    }
854    if deposit_reserve.last_update.is_stale(clock.slot)? {
855        msg!("Deposit reserve is stale and must be refreshed in the current slot");
856        return Err(LendingError::ReserveStale.into());
857    }
858    if deposit_reserve.config.loan_to_value_ratio == 0 {
859        msg!("Deposit reserve has collateral disabled for borrowing");
860        return Err(LendingError::ReserveCollateralDisabled.into());
861    }
862
863    let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
864    if obligation_info.owner != program_id {
865        msg!("Obligation provided is not owned by the lending program");
866        return Err(LendingError::InvalidAccountOwner.into());
867    }
868    if &obligation.lending_market != lending_market_info.key {
869        msg!("Obligation lending market does not match the lending market provided");
870        return Err(LendingError::InvalidAccountInput.into());
871    }
872    if &obligation.owner != obligation_owner_info.key {
873        msg!("Obligation owner does not match the obligation owner provided");
874        return Err(LendingError::InvalidObligationOwner.into());
875    }
876    if !obligation_owner_info.is_signer {
877        msg!("Obligation owner provided must be a signer");
878        return Err(LendingError::InvalidSigner.into());
879    }
880
881    obligation
882        .find_or_add_collateral_to_deposits(*deposit_reserve_info.key)?
883        .deposit(collateral_amount)?;
884    obligation.last_update.mark_stale();
885    Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
886
887    spl_token_transfer(TokenTransferParams {
888        source: source_collateral_info.clone(),
889        destination: destination_collateral_info.clone(),
890        amount: collateral_amount,
891        authority: user_transfer_authority_info.clone(),
892        authority_signer_seeds: &[],
893        token_program: token_program_id.clone(),
894    })?;
895
896    Ok(())
897}
898
899#[inline(never)] // avoid stack frame limit
900fn process_withdraw_obligation_collateral(
901    program_id: &Pubkey,
902    collateral_amount: u64,
903    accounts: &[AccountInfo],
904) -> ProgramResult {
905    if collateral_amount == 0 {
906        msg!("Collateral amount provided cannot be zero");
907        return Err(LendingError::InvalidAmount.into());
908    }
909
910    let account_info_iter = &mut accounts.iter();
911    let source_collateral_info = next_account_info(account_info_iter)?;
912    let destination_collateral_info = next_account_info(account_info_iter)?;
913    let withdraw_reserve_info = next_account_info(account_info_iter)?;
914    let obligation_info = next_account_info(account_info_iter)?;
915    let lending_market_info = next_account_info(account_info_iter)?;
916    let lending_market_authority_info = next_account_info(account_info_iter)?;
917    let obligation_owner_info = next_account_info(account_info_iter)?;
918    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
919    let token_program_id = next_account_info(account_info_iter)?;
920
921    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
922    if lending_market_info.owner != program_id {
923        msg!("Lending market provided is not owned by the lending program");
924        return Err(LendingError::InvalidAccountOwner.into());
925    }
926    if &lending_market.token_program_id != token_program_id.key {
927        msg!("Lending market token program does not match the token program provided");
928        return Err(LendingError::InvalidTokenProgram.into());
929    }
930
931    let withdraw_reserve = Reserve::unpack(&withdraw_reserve_info.data.borrow())?;
932    if withdraw_reserve_info.owner != program_id {
933        msg!("Withdraw reserve provided is not owned by the lending program");
934        return Err(LendingError::InvalidAccountOwner.into());
935    }
936    if &withdraw_reserve.lending_market != lending_market_info.key {
937        msg!("Withdraw reserve lending market does not match the lending market provided");
938        return Err(LendingError::InvalidAccountInput.into());
939    }
940    if &withdraw_reserve.collateral.supply_pubkey != source_collateral_info.key {
941        msg!("Withdraw reserve collateral supply must be used as the source collateral provided");
942        return Err(LendingError::InvalidAccountInput.into());
943    }
944    if &withdraw_reserve.collateral.supply_pubkey == destination_collateral_info.key {
945        msg!("Withdraw reserve collateral supply cannot be used as the destination collateral provided");
946        return Err(LendingError::InvalidAccountInput.into());
947    }
948    if withdraw_reserve.last_update.is_stale(clock.slot)? {
949        msg!("Withdraw reserve is stale and must be refreshed in the current slot");
950        return Err(LendingError::ReserveStale.into());
951    }
952
953    let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
954    if obligation_info.owner != program_id {
955        msg!("Obligation provided is not owned by the lending program");
956        return Err(LendingError::InvalidAccountOwner.into());
957    }
958    if &obligation.lending_market != lending_market_info.key {
959        msg!("Obligation lending market does not match the lending market provided");
960        return Err(LendingError::InvalidAccountInput.into());
961    }
962    if &obligation.owner != obligation_owner_info.key {
963        msg!("Obligation owner does not match the obligation owner provided");
964        return Err(LendingError::InvalidObligationOwner.into());
965    }
966    if !obligation_owner_info.is_signer {
967        msg!("Obligation owner provided must be a signer");
968        return Err(LendingError::InvalidSigner.into());
969    }
970    if obligation.last_update.is_stale(clock.slot)? {
971        msg!("Obligation is stale and must be refreshed in the current slot");
972        return Err(LendingError::ObligationStale.into());
973    }
974
975    let (collateral, collateral_index) =
976        obligation.find_collateral_in_deposits(*withdraw_reserve_info.key)?;
977    if collateral.deposited_amount == 0 {
978        msg!("Collateral deposited amount is zero");
979        return Err(LendingError::ObligationCollateralEmpty.into());
980    }
981
982    let authority_signer_seeds = &[
983        lending_market_info.key.as_ref(),
984        &[lending_market.bump_seed],
985    ];
986    let lending_market_authority_pubkey =
987        Pubkey::create_program_address(authority_signer_seeds, program_id)?;
988    if &lending_market_authority_pubkey != lending_market_authority_info.key {
989        msg!(
990            "Derived lending market authority does not match the lending market authority provided"
991        );
992        return Err(LendingError::InvalidMarketAuthority.into());
993    }
994
995    let withdraw_amount = if obligation.borrows.is_empty() {
996        if collateral_amount == u64::MAX {
997            collateral.deposited_amount
998        } else {
999            collateral.deposited_amount.min(collateral_amount)
1000        }
1001    } else if obligation.deposited_value == Decimal::zero() {
1002        msg!("Obligation deposited value is zero");
1003        return Err(LendingError::ObligationDepositsZero.into());
1004    } else {
1005        let max_withdraw_value = obligation.max_withdraw_value(Rate::from_percent(
1006            withdraw_reserve.config.loan_to_value_ratio,
1007        ))?;
1008        if max_withdraw_value == Decimal::zero() {
1009            msg!("Maximum withdraw value is zero");
1010            return Err(LendingError::WithdrawTooLarge.into());
1011        }
1012
1013        let withdraw_amount = if collateral_amount == u64::MAX {
1014            let withdraw_value = max_withdraw_value.min(collateral.market_value);
1015            let withdraw_pct = withdraw_value.try_div(collateral.market_value)?;
1016            withdraw_pct
1017                .try_mul(collateral.deposited_amount)?
1018                .try_floor_u64()?
1019                .min(collateral.deposited_amount)
1020        } else {
1021            let withdraw_amount = collateral_amount.min(collateral.deposited_amount);
1022            let withdraw_pct =
1023                Decimal::from(withdraw_amount).try_div(collateral.deposited_amount)?;
1024            let withdraw_value = collateral.market_value.try_mul(withdraw_pct)?;
1025            if withdraw_value > max_withdraw_value {
1026                msg!("Withdraw value cannot exceed maximum withdraw value");
1027                return Err(LendingError::WithdrawTooLarge.into());
1028            }
1029            withdraw_amount
1030        };
1031        if withdraw_amount == 0 {
1032            msg!("Withdraw amount is too small to transfer collateral");
1033            return Err(LendingError::WithdrawTooSmall.into());
1034        }
1035        withdraw_amount
1036    };
1037
1038    obligation.withdraw(withdraw_amount, collateral_index)?;
1039    obligation.last_update.mark_stale();
1040    Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
1041
1042    spl_token_transfer(TokenTransferParams {
1043        source: source_collateral_info.clone(),
1044        destination: destination_collateral_info.clone(),
1045        amount: withdraw_amount,
1046        authority: lending_market_authority_info.clone(),
1047        authority_signer_seeds,
1048        token_program: token_program_id.clone(),
1049    })?;
1050
1051    Ok(())
1052}
1053
1054#[inline(never)] // avoid stack frame limit
1055fn process_borrow_obligation_liquidity(
1056    program_id: &Pubkey,
1057    liquidity_amount: u64,
1058    accounts: &[AccountInfo],
1059) -> ProgramResult {
1060    if liquidity_amount == 0 {
1061        msg!("Liquidity amount provided cannot be zero");
1062        return Err(LendingError::InvalidAmount.into());
1063    }
1064
1065    let account_info_iter = &mut accounts.iter();
1066    let source_liquidity_info = next_account_info(account_info_iter)?;
1067    let destination_liquidity_info = next_account_info(account_info_iter)?;
1068    let borrow_reserve_info = next_account_info(account_info_iter)?;
1069    let borrow_reserve_liquidity_fee_receiver_info = next_account_info(account_info_iter)?;
1070    let obligation_info = next_account_info(account_info_iter)?;
1071    let lending_market_info = next_account_info(account_info_iter)?;
1072    let lending_market_authority_info = next_account_info(account_info_iter)?;
1073    let obligation_owner_info = next_account_info(account_info_iter)?;
1074    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
1075    let token_program_id = next_account_info(account_info_iter)?;
1076
1077    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
1078    if lending_market_info.owner != program_id {
1079        msg!("Lending market provided is not owned by the lending program");
1080        return Err(LendingError::InvalidAccountOwner.into());
1081    }
1082    if &lending_market.token_program_id != token_program_id.key {
1083        msg!("Lending market token program does not match the token program provided");
1084        return Err(LendingError::InvalidTokenProgram.into());
1085    }
1086
1087    let mut borrow_reserve = Reserve::unpack(&borrow_reserve_info.data.borrow())?;
1088    if borrow_reserve_info.owner != program_id {
1089        msg!("Borrow reserve provided is not owned by the lending program");
1090        return Err(LendingError::InvalidAccountOwner.into());
1091    }
1092    if &borrow_reserve.lending_market != lending_market_info.key {
1093        msg!("Borrow reserve lending market does not match the lending market provided");
1094        return Err(LendingError::InvalidAccountInput.into());
1095    }
1096    if &borrow_reserve.liquidity.supply_pubkey != source_liquidity_info.key {
1097        msg!("Borrow reserve liquidity supply must be used as the source liquidity provided");
1098        return Err(LendingError::InvalidAccountInput.into());
1099    }
1100    if &borrow_reserve.liquidity.supply_pubkey == destination_liquidity_info.key {
1101        msg!(
1102            "Borrow reserve liquidity supply cannot be used as the destination liquidity provided"
1103        );
1104        return Err(LendingError::InvalidAccountInput.into());
1105    }
1106    if &borrow_reserve.liquidity.fee_receiver != borrow_reserve_liquidity_fee_receiver_info.key {
1107        msg!("Borrow reserve liquidity fee receiver does not match the borrow reserve liquidity fee receiver provided");
1108        return Err(LendingError::InvalidAccountInput.into());
1109    }
1110    if borrow_reserve.last_update.is_stale(clock.slot)? {
1111        msg!("Borrow reserve is stale and must be refreshed in the current slot");
1112        return Err(LendingError::ReserveStale.into());
1113    }
1114
1115    let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
1116    if obligation_info.owner != program_id {
1117        msg!("Obligation provided is not owned by the lending program");
1118        return Err(LendingError::InvalidAccountOwner.into());
1119    }
1120    if &obligation.lending_market != lending_market_info.key {
1121        msg!("Obligation lending market does not match the lending market provided");
1122        return Err(LendingError::InvalidAccountInput.into());
1123    }
1124    if &obligation.owner != obligation_owner_info.key {
1125        msg!("Obligation owner does not match the obligation owner provided");
1126        return Err(LendingError::InvalidObligationOwner.into());
1127    }
1128    if !obligation_owner_info.is_signer {
1129        msg!("Obligation owner provided must be a signer");
1130        return Err(LendingError::InvalidSigner.into());
1131    }
1132    if obligation.last_update.is_stale(clock.slot)? {
1133        msg!("Obligation is stale and must be refreshed in the current slot");
1134        return Err(LendingError::ObligationStale.into());
1135    }
1136    if obligation.deposits.is_empty() {
1137        msg!("Obligation has no deposits to borrow against");
1138        return Err(LendingError::ObligationDepositsEmpty.into());
1139    }
1140    if obligation.deposited_value == Decimal::zero() {
1141        msg!("Obligation deposits have zero value");
1142        return Err(LendingError::ObligationDepositsZero.into());
1143    }
1144
1145    let authority_signer_seeds = &[
1146        lending_market_info.key.as_ref(),
1147        &[lending_market.bump_seed],
1148    ];
1149    let lending_market_authority_pubkey =
1150        Pubkey::create_program_address(authority_signer_seeds, program_id)?;
1151    if &lending_market_authority_pubkey != lending_market_authority_info.key {
1152        msg!(
1153            "Derived lending market authority does not match the lending market authority provided"
1154        );
1155        return Err(LendingError::InvalidMarketAuthority.into());
1156    }
1157
1158    let remaining_borrow_value = obligation.remaining_borrow_value()?;
1159    if remaining_borrow_value == Decimal::zero() {
1160        msg!("Remaining borrow value is zero");
1161        return Err(LendingError::BorrowTooLarge.into());
1162    }
1163
1164    let CalculateBorrowResult {
1165        borrow_amount,
1166        receive_amount,
1167        borrow_fee,
1168        host_fee,
1169    } = borrow_reserve.calculate_borrow(liquidity_amount, remaining_borrow_value)?;
1170
1171    if receive_amount == 0 {
1172        msg!("Borrow amount is too small to receive liquidity after fees");
1173        return Err(LendingError::BorrowTooSmall.into());
1174    }
1175
1176    borrow_reserve.liquidity.borrow(borrow_amount)?;
1177    borrow_reserve.last_update.mark_stale();
1178    Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?;
1179
1180    obligation
1181        .find_or_add_liquidity_to_borrows(*borrow_reserve_info.key)?
1182        .borrow(borrow_amount)?;
1183    obligation.last_update.mark_stale();
1184    Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
1185
1186    let mut owner_fee = borrow_fee;
1187    if let Ok(host_fee_receiver_info) = next_account_info(account_info_iter) {
1188        if host_fee > 0 {
1189            owner_fee = owner_fee
1190                .checked_sub(host_fee)
1191                .ok_or(LendingError::MathOverflow)?;
1192
1193            spl_token_transfer(TokenTransferParams {
1194                source: source_liquidity_info.clone(),
1195                destination: host_fee_receiver_info.clone(),
1196                amount: host_fee,
1197                authority: lending_market_authority_info.clone(),
1198                authority_signer_seeds,
1199                token_program: token_program_id.clone(),
1200            })?;
1201        }
1202    }
1203    if owner_fee > 0 {
1204        spl_token_transfer(TokenTransferParams {
1205            source: source_liquidity_info.clone(),
1206            destination: borrow_reserve_liquidity_fee_receiver_info.clone(),
1207            amount: owner_fee,
1208            authority: lending_market_authority_info.clone(),
1209            authority_signer_seeds,
1210            token_program: token_program_id.clone(),
1211        })?;
1212    }
1213
1214    spl_token_transfer(TokenTransferParams {
1215        source: source_liquidity_info.clone(),
1216        destination: destination_liquidity_info.clone(),
1217        amount: receive_amount,
1218        authority: lending_market_authority_info.clone(),
1219        authority_signer_seeds,
1220        token_program: token_program_id.clone(),
1221    })?;
1222
1223    Ok(())
1224}
1225
1226#[inline(never)] // avoid stack frame limit
1227fn process_repay_obligation_liquidity(
1228    program_id: &Pubkey,
1229    liquidity_amount: u64,
1230    accounts: &[AccountInfo],
1231) -> ProgramResult {
1232    if liquidity_amount == 0 {
1233        msg!("Liquidity amount provided cannot be zero");
1234        return Err(LendingError::InvalidAmount.into());
1235    }
1236
1237    let account_info_iter = &mut accounts.iter();
1238    let source_liquidity_info = next_account_info(account_info_iter)?;
1239    let destination_liquidity_info = next_account_info(account_info_iter)?;
1240    let repay_reserve_info = next_account_info(account_info_iter)?;
1241    let obligation_info = next_account_info(account_info_iter)?;
1242    let lending_market_info = next_account_info(account_info_iter)?;
1243    let user_transfer_authority_info = next_account_info(account_info_iter)?;
1244    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
1245    let token_program_id = next_account_info(account_info_iter)?;
1246
1247    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
1248    if lending_market_info.owner != program_id {
1249        msg!("Lending market provided is not owned by the lending program");
1250        return Err(LendingError::InvalidAccountOwner.into());
1251    }
1252    if &lending_market.token_program_id != token_program_id.key {
1253        msg!("Lending market token program does not match the token program provided");
1254        return Err(LendingError::InvalidTokenProgram.into());
1255    }
1256
1257    let mut repay_reserve = Reserve::unpack(&repay_reserve_info.data.borrow())?;
1258    if repay_reserve_info.owner != program_id {
1259        msg!("Repay reserve provided is not owned by the lending program");
1260        return Err(LendingError::InvalidAccountOwner.into());
1261    }
1262    if &repay_reserve.lending_market != lending_market_info.key {
1263        msg!("Repay reserve lending market does not match the lending market provided");
1264        return Err(LendingError::InvalidAccountInput.into());
1265    }
1266    if &repay_reserve.liquidity.supply_pubkey == source_liquidity_info.key {
1267        msg!("Repay reserve liquidity supply cannot be used as the source liquidity provided");
1268        return Err(LendingError::InvalidAccountInput.into());
1269    }
1270    if &repay_reserve.liquidity.supply_pubkey != destination_liquidity_info.key {
1271        msg!("Repay reserve liquidity supply must be used as the destination liquidity provided");
1272        return Err(LendingError::InvalidAccountInput.into());
1273    }
1274    if repay_reserve.last_update.is_stale(clock.slot)? {
1275        msg!("Repay reserve is stale and must be refreshed in the current slot");
1276        return Err(LendingError::ReserveStale.into());
1277    }
1278
1279    let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
1280    if obligation_info.owner != program_id {
1281        msg!("Obligation provided is not owned by the lending program");
1282        return Err(LendingError::InvalidAccountOwner.into());
1283    }
1284    if &obligation.lending_market != lending_market_info.key {
1285        msg!("Obligation lending market does not match the lending market provided");
1286        return Err(LendingError::InvalidAccountInput.into());
1287    }
1288    if obligation.last_update.is_stale(clock.slot)? {
1289        msg!("Obligation is stale and must be refreshed in the current slot");
1290        return Err(LendingError::ObligationStale.into());
1291    }
1292
1293    let (liquidity, liquidity_index) =
1294        obligation.find_liquidity_in_borrows(*repay_reserve_info.key)?;
1295    if liquidity.borrowed_amount_wads == Decimal::zero() {
1296        msg!("Liquidity borrowed amount is zero");
1297        return Err(LendingError::ObligationLiquidityEmpty.into());
1298    }
1299
1300    let CalculateRepayResult {
1301        settle_amount,
1302        repay_amount,
1303    } = repay_reserve.calculate_repay(liquidity_amount, liquidity.borrowed_amount_wads)?;
1304
1305    if repay_amount == 0 {
1306        msg!("Repay amount is too small to transfer liquidity");
1307        return Err(LendingError::RepayTooSmall.into());
1308    }
1309
1310    repay_reserve.liquidity.repay(repay_amount, settle_amount)?;
1311    repay_reserve.last_update.mark_stale();
1312    Reserve::pack(repay_reserve, &mut repay_reserve_info.data.borrow_mut())?;
1313
1314    obligation.repay(settle_amount, liquidity_index)?;
1315    obligation.last_update.mark_stale();
1316    Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
1317
1318    spl_token_transfer(TokenTransferParams {
1319        source: source_liquidity_info.clone(),
1320        destination: destination_liquidity_info.clone(),
1321        amount: repay_amount,
1322        authority: user_transfer_authority_info.clone(),
1323        authority_signer_seeds: &[],
1324        token_program: token_program_id.clone(),
1325    })?;
1326
1327    Ok(())
1328}
1329
1330#[inline(never)] // avoid stack frame limit
1331fn process_liquidate_obligation(
1332    program_id: &Pubkey,
1333    liquidity_amount: u64,
1334    accounts: &[AccountInfo],
1335) -> ProgramResult {
1336    if liquidity_amount == 0 {
1337        msg!("Liquidity amount provided cannot be zero");
1338        return Err(LendingError::InvalidAmount.into());
1339    }
1340
1341    let account_info_iter = &mut accounts.iter();
1342    let source_liquidity_info = next_account_info(account_info_iter)?;
1343    let destination_collateral_info = next_account_info(account_info_iter)?;
1344    let repay_reserve_info = next_account_info(account_info_iter)?;
1345    let repay_reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
1346    let withdraw_reserve_info = next_account_info(account_info_iter)?;
1347    let withdraw_reserve_collateral_supply_info = next_account_info(account_info_iter)?;
1348    let obligation_info = next_account_info(account_info_iter)?;
1349    let lending_market_info = next_account_info(account_info_iter)?;
1350    let lending_market_authority_info = next_account_info(account_info_iter)?;
1351    let user_transfer_authority_info = next_account_info(account_info_iter)?;
1352    let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
1353    let token_program_id = next_account_info(account_info_iter)?;
1354
1355    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
1356    if lending_market_info.owner != program_id {
1357        msg!("Lending market provided is not owned by the lending program");
1358        return Err(LendingError::InvalidAccountOwner.into());
1359    }
1360    if &lending_market.token_program_id != token_program_id.key {
1361        msg!("Lending market token program does not match the token program provided");
1362        return Err(LendingError::InvalidTokenProgram.into());
1363    }
1364
1365    let mut repay_reserve = Reserve::unpack(&repay_reserve_info.data.borrow())?;
1366    if repay_reserve_info.owner != program_id {
1367        msg!("Repay reserve provided is not owned by the lending program");
1368        return Err(LendingError::InvalidAccountOwner.into());
1369    }
1370    if &repay_reserve.lending_market != lending_market_info.key {
1371        msg!("Repay reserve lending market does not match the lending market provided");
1372        return Err(LendingError::InvalidAccountInput.into());
1373    }
1374    if &repay_reserve.liquidity.supply_pubkey != repay_reserve_liquidity_supply_info.key {
1375        msg!("Repay reserve liquidity supply does not match the repay reserve liquidity supply provided");
1376        return Err(LendingError::InvalidAccountInput.into());
1377    }
1378    if &repay_reserve.liquidity.supply_pubkey == source_liquidity_info.key {
1379        msg!("Repay reserve liquidity supply cannot be used as the source liquidity provided");
1380        return Err(LendingError::InvalidAccountInput.into());
1381    }
1382    if &repay_reserve.collateral.supply_pubkey == destination_collateral_info.key {
1383        msg!(
1384            "Repay reserve collateral supply cannot be used as the destination collateral provided"
1385        );
1386        return Err(LendingError::InvalidAccountInput.into());
1387    }
1388    if repay_reserve.last_update.is_stale(clock.slot)? {
1389        msg!("Repay reserve is stale and must be refreshed in the current slot");
1390        return Err(LendingError::ReserveStale.into());
1391    }
1392
1393    let withdraw_reserve = Reserve::unpack(&withdraw_reserve_info.data.borrow())?;
1394    if withdraw_reserve_info.owner != program_id {
1395        msg!("Withdraw reserve provided is not owned by the lending program");
1396        return Err(LendingError::InvalidAccountOwner.into());
1397    }
1398    if &withdraw_reserve.lending_market != lending_market_info.key {
1399        msg!("Withdraw reserve lending market does not match the lending market provided");
1400        return Err(LendingError::InvalidAccountInput.into());
1401    }
1402    if &withdraw_reserve.collateral.supply_pubkey != withdraw_reserve_collateral_supply_info.key {
1403        msg!("Withdraw reserve collateral supply does not match the withdraw reserve collateral supply provided");
1404        return Err(LendingError::InvalidAccountInput.into());
1405    }
1406    if &withdraw_reserve.liquidity.supply_pubkey == source_liquidity_info.key {
1407        msg!("Withdraw reserve liquidity supply cannot be used as the source liquidity provided");
1408        return Err(LendingError::InvalidAccountInput.into());
1409    }
1410    if &withdraw_reserve.collateral.supply_pubkey == destination_collateral_info.key {
1411        msg!("Withdraw reserve collateral supply cannot be used as the destination collateral provided");
1412        return Err(LendingError::InvalidAccountInput.into());
1413    }
1414    if withdraw_reserve.last_update.is_stale(clock.slot)? {
1415        msg!("Withdraw reserve is stale and must be refreshed in the current slot");
1416        return Err(LendingError::ReserveStale.into());
1417    }
1418
1419    let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
1420    if obligation_info.owner != program_id {
1421        msg!("Obligation provided is not owned by the lending program");
1422        return Err(LendingError::InvalidAccountOwner.into());
1423    }
1424    if &obligation.lending_market != lending_market_info.key {
1425        msg!("Obligation lending market does not match the lending market provided");
1426        return Err(LendingError::InvalidAccountInput.into());
1427    }
1428    if obligation.last_update.is_stale(clock.slot)? {
1429        msg!("Obligation is stale and must be refreshed in the current slot");
1430        return Err(LendingError::ObligationStale.into());
1431    }
1432    if obligation.deposited_value == Decimal::zero() {
1433        msg!("Obligation deposited value is zero");
1434        return Err(LendingError::ObligationDepositsZero.into());
1435    }
1436    if obligation.borrowed_value == Decimal::zero() {
1437        msg!("Obligation borrowed value is zero");
1438        return Err(LendingError::ObligationBorrowsZero.into());
1439    }
1440    if obligation.borrowed_value < obligation.unhealthy_borrow_value {
1441        msg!("Obligation is healthy and cannot be liquidated");
1442        return Err(LendingError::ObligationHealthy.into());
1443    }
1444
1445    let (liquidity, liquidity_index) =
1446        obligation.find_liquidity_in_borrows(*repay_reserve_info.key)?;
1447    if liquidity.market_value == Decimal::zero() {
1448        msg!("Obligation borrow value is zero");
1449        return Err(LendingError::ObligationLiquidityEmpty.into());
1450    }
1451
1452    let (collateral, collateral_index) =
1453        obligation.find_collateral_in_deposits(*withdraw_reserve_info.key)?;
1454    if collateral.market_value == Decimal::zero() {
1455        msg!("Obligation deposit value is zero");
1456        return Err(LendingError::ObligationCollateralEmpty.into());
1457    }
1458
1459    let authority_signer_seeds = &[
1460        lending_market_info.key.as_ref(),
1461        &[lending_market.bump_seed],
1462    ];
1463    let lending_market_authority_pubkey =
1464        Pubkey::create_program_address(authority_signer_seeds, program_id)?;
1465    if &lending_market_authority_pubkey != lending_market_authority_info.key {
1466        msg!(
1467            "Derived lending market authority does not match the lending market authority provided"
1468        );
1469        return Err(LendingError::InvalidMarketAuthority.into());
1470    }
1471
1472    let CalculateLiquidationResult {
1473        settle_amount,
1474        repay_amount,
1475        withdraw_amount,
1476    } = withdraw_reserve.calculate_liquidation(
1477        liquidity_amount,
1478        &obligation,
1479        liquidity,
1480        collateral,
1481    )?;
1482
1483    if repay_amount == 0 {
1484        msg!("Liquidation is too small to transfer liquidity");
1485        return Err(LendingError::LiquidationTooSmall.into());
1486    }
1487    if withdraw_amount == 0 {
1488        msg!("Liquidation is too small to receive collateral");
1489        return Err(LendingError::LiquidationTooSmall.into());
1490    }
1491
1492    repay_reserve.liquidity.repay(repay_amount, settle_amount)?;
1493    repay_reserve.last_update.mark_stale();
1494    Reserve::pack(repay_reserve, &mut repay_reserve_info.data.borrow_mut())?;
1495
1496    obligation.repay(settle_amount, liquidity_index)?;
1497    obligation.withdraw(withdraw_amount, collateral_index)?;
1498    obligation.last_update.mark_stale();
1499    Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
1500
1501    spl_token_transfer(TokenTransferParams {
1502        source: source_liquidity_info.clone(),
1503        destination: repay_reserve_liquidity_supply_info.clone(),
1504        amount: repay_amount,
1505        authority: user_transfer_authority_info.clone(),
1506        authority_signer_seeds: &[],
1507        token_program: token_program_id.clone(),
1508    })?;
1509
1510    spl_token_transfer(TokenTransferParams {
1511        source: withdraw_reserve_collateral_supply_info.clone(),
1512        destination: destination_collateral_info.clone(),
1513        amount: withdraw_amount,
1514        authority: lending_market_authority_info.clone(),
1515        authority_signer_seeds,
1516        token_program: token_program_id.clone(),
1517    })?;
1518
1519    Ok(())
1520}
1521
1522#[inline(never)] // avoid stack frame limit
1523fn process_flash_loan(
1524    program_id: &Pubkey,
1525    liquidity_amount: u64,
1526    accounts: &[AccountInfo],
1527) -> ProgramResult {
1528    if liquidity_amount == 0 {
1529        msg!("Liquidity amount provided cannot be zero");
1530        return Err(LendingError::InvalidAmount.into());
1531    }
1532
1533    let account_info_iter = &mut accounts.iter();
1534    let source_liquidity_info = next_account_info(account_info_iter)?;
1535    let destination_liquidity_info = next_account_info(account_info_iter)?;
1536    let reserve_info = next_account_info(account_info_iter)?;
1537    let reserve_liquidity_fee_receiver_info = next_account_info(account_info_iter)?;
1538    let host_fee_receiver_info = next_account_info(account_info_iter)?;
1539    let lending_market_info = next_account_info(account_info_iter)?;
1540    let lending_market_authority_info = next_account_info(account_info_iter)?;
1541    let token_program_id = next_account_info(account_info_iter)?;
1542    let flash_loan_receiver_program_id = next_account_info(account_info_iter)?;
1543
1544    if program_id == flash_loan_receiver_program_id.key {
1545        msg!("Lending program cannot be used as the flash loan receiver program provided");
1546        return Err(LendingError::InvalidFlashLoanReceiverProgram.into());
1547    }
1548
1549    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
1550    if lending_market_info.owner != program_id {
1551        return Err(LendingError::InvalidAccountOwner.into());
1552    }
1553    if &lending_market.token_program_id != token_program_id.key {
1554        msg!("Lending market token program does not match the token program provided");
1555        return Err(LendingError::InvalidTokenProgram.into());
1556    }
1557
1558    let authority_signer_seeds = &[
1559        lending_market_info.key.as_ref(),
1560        &[lending_market.bump_seed],
1561    ];
1562    let lending_market_authority_pubkey =
1563        Pubkey::create_program_address(authority_signer_seeds, program_id)?;
1564    if &lending_market_authority_pubkey != lending_market_authority_info.key {
1565        msg!(
1566            "Derived lending market authority does not match the lending market authority provided"
1567        );
1568        return Err(LendingError::InvalidMarketAuthority.into());
1569    }
1570
1571    let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
1572    if reserve_info.owner != program_id {
1573        msg!("Reserve provided is not owned by the lending program");
1574        return Err(LendingError::InvalidAccountOwner.into());
1575    }
1576    if &reserve.lending_market != lending_market_info.key {
1577        msg!("Invalid reserve lending market account");
1578        return Err(LendingError::InvalidAccountInput.into());
1579    }
1580    if &reserve.liquidity.supply_pubkey != source_liquidity_info.key {
1581        msg!("Reserve liquidity supply must be used as the source liquidity provided");
1582        return Err(LendingError::InvalidAccountInput.into());
1583    }
1584    if &reserve.liquidity.fee_receiver != reserve_liquidity_fee_receiver_info.key {
1585        msg!("Reserve liquidity fee receiver does not match the reserve liquidity fee receiver provided");
1586        return Err(LendingError::InvalidAccountInput.into());
1587    }
1588
1589    // @FIXME: if u64::MAX is flash loaned, fees should be inclusive as with ordinary borrows
1590    let flash_loan_amount = if liquidity_amount == u64::MAX {
1591        reserve.liquidity.available_amount
1592    } else {
1593        liquidity_amount
1594    };
1595
1596    let flash_loan_amount_decimal = Decimal::from(flash_loan_amount);
1597    let (origination_fee, host_fee) = reserve
1598        .config
1599        .fees
1600        .calculate_flash_loan_fees(flash_loan_amount_decimal)?;
1601
1602    let balance_before_flash_loan = Account::unpack(&source_liquidity_info.data.borrow())?.amount;
1603    let expected_balance_after_flash_loan = balance_before_flash_loan
1604        .checked_add(origination_fee)
1605        .ok_or(LendingError::MathOverflow)?;
1606    let returned_amount_required = flash_loan_amount
1607        .checked_add(origination_fee)
1608        .ok_or(LendingError::MathOverflow)?;
1609
1610    let mut flash_loan_instruction_accounts = vec![
1611        AccountMeta::new(*destination_liquidity_info.key, false),
1612        AccountMeta::new(*source_liquidity_info.key, false),
1613        AccountMeta::new_readonly(*token_program_id.key, false),
1614    ];
1615    let mut flash_loan_instruction_account_infos = vec![
1616        destination_liquidity_info.clone(),
1617        flash_loan_receiver_program_id.clone(),
1618        source_liquidity_info.clone(),
1619        token_program_id.clone(),
1620    ];
1621    for account_info in account_info_iter {
1622        flash_loan_instruction_accounts.push(AccountMeta {
1623            pubkey: *account_info.key,
1624            is_signer: account_info.is_signer,
1625            is_writable: account_info.is_writable,
1626        });
1627        flash_loan_instruction_account_infos.push(account_info.clone());
1628    }
1629
1630    reserve.liquidity.borrow(flash_loan_amount_decimal)?;
1631    Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
1632
1633    spl_token_transfer(TokenTransferParams {
1634        source: source_liquidity_info.clone(),
1635        destination: destination_liquidity_info.clone(),
1636        amount: flash_loan_amount,
1637        authority: lending_market_authority_info.clone(),
1638        authority_signer_seeds,
1639        token_program: token_program_id.clone(),
1640    })?;
1641
1642    const RECEIVE_FLASH_LOAN_INSTRUCTION_DATA_SIZE: usize = 9;
1643    // @FIXME: don't use 0 to indicate a flash loan receiver instruction https://git.io/JGzz9
1644    const RECEIVE_FLASH_LOAN_INSTRUCTION_TAG: u8 = 0u8;
1645
1646    let mut data = Vec::with_capacity(RECEIVE_FLASH_LOAN_INSTRUCTION_DATA_SIZE);
1647    data.push(RECEIVE_FLASH_LOAN_INSTRUCTION_TAG);
1648    data.extend_from_slice(&returned_amount_required.to_le_bytes());
1649
1650    invoke(
1651        &Instruction {
1652            program_id: *flash_loan_receiver_program_id.key,
1653            accounts: flash_loan_instruction_accounts,
1654            data,
1655        },
1656        &flash_loan_instruction_account_infos[..],
1657    )?;
1658
1659    reserve = Reserve::unpack(&reserve_info.data.borrow())?;
1660    reserve
1661        .liquidity
1662        .repay(flash_loan_amount, flash_loan_amount_decimal)?;
1663    Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
1664
1665    let actual_balance_after_flash_loan =
1666        Account::unpack(&source_liquidity_info.data.borrow())?.amount;
1667    if actual_balance_after_flash_loan < expected_balance_after_flash_loan {
1668        msg!("Insufficient reserve liquidity after flash loan");
1669        return Err(LendingError::NotEnoughLiquidityAfterFlashLoan.into());
1670    }
1671
1672    let mut owner_fee = origination_fee;
1673    if host_fee > 0 {
1674        owner_fee = owner_fee
1675            .checked_sub(host_fee)
1676            .ok_or(LendingError::MathOverflow)?;
1677        spl_token_transfer(TokenTransferParams {
1678            source: source_liquidity_info.clone(),
1679            destination: host_fee_receiver_info.clone(),
1680            amount: host_fee,
1681            authority: lending_market_authority_info.clone(),
1682            authority_signer_seeds,
1683            token_program: token_program_id.clone(),
1684        })?;
1685    }
1686
1687    if owner_fee > 0 {
1688        spl_token_transfer(TokenTransferParams {
1689            source: source_liquidity_info.clone(),
1690            destination: reserve_liquidity_fee_receiver_info.clone(),
1691            amount: owner_fee,
1692            authority: lending_market_authority_info.clone(),
1693            authority_signer_seeds,
1694            token_program: token_program_id.clone(),
1695        })?;
1696    }
1697
1698    Ok(())
1699}
1700
1701fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
1702    if !rent.is_exempt(account_info.lamports(), account_info.data_len()) {
1703        msg!(&rent.minimum_balance(account_info.data_len()).to_string());
1704        Err(LendingError::NotRentExempt.into())
1705    } else {
1706        Ok(())
1707    }
1708}
1709
1710fn assert_uninitialized<T: Pack + IsInitialized>(
1711    account_info: &AccountInfo,
1712) -> Result<T, ProgramError> {
1713    let account: T = T::unpack_unchecked(&account_info.data.borrow())?;
1714    if account.is_initialized() {
1715        Err(LendingError::AlreadyInitialized.into())
1716    } else {
1717        Ok(account)
1718    }
1719}
1720
1721/// Unpacks a spl_token `Mint`.
1722fn unpack_mint(data: &[u8]) -> Result<Mint, LendingError> {
1723    Mint::unpack(data).map_err(|_| LendingError::InvalidTokenMint)
1724}
1725
1726fn get_pyth_product_quote_currency(pyth_product: &pyth::Product) -> Result<[u8; 32], ProgramError> {
1727    const LEN: usize = 14;
1728    const KEY: &[u8; LEN] = b"quote_currency";
1729
1730    let mut start = 0;
1731    while start < pyth::PROD_ATTR_SIZE {
1732        let mut length = pyth_product.attr[start] as usize;
1733        start += 1;
1734
1735        if length == LEN {
1736            let mut end = start + length;
1737            if end > pyth::PROD_ATTR_SIZE {
1738                msg!("Pyth product attribute key length too long");
1739                return Err(LendingError::InvalidOracleConfig.into());
1740            }
1741
1742            let key = &pyth_product.attr[start..end];
1743            if key == KEY {
1744                start += length;
1745                length = pyth_product.attr[start] as usize;
1746                start += 1;
1747
1748                end = start + length;
1749                if length > 32 || end > pyth::PROD_ATTR_SIZE {
1750                    msg!("Pyth product quote currency value too long");
1751                    return Err(LendingError::InvalidOracleConfig.into());
1752                }
1753
1754                let mut value = [0u8; 32];
1755                value[0..length].copy_from_slice(&pyth_product.attr[start..end]);
1756                return Ok(value);
1757            }
1758        }
1759
1760        start += length;
1761        start += 1 + pyth_product.attr[start] as usize;
1762    }
1763
1764    msg!("Pyth product quote currency not found");
1765    Err(LendingError::InvalidOracleConfig.into())
1766}
1767
1768fn get_pyth_price(pyth_price_info: &AccountInfo, clock: &Clock) -> Result<Decimal, ProgramError> {
1769    const STALE_AFTER_SLOTS_ELAPSED: u64 = 5;
1770
1771    let pyth_price_data = pyth_price_info.try_borrow_data()?;
1772    let pyth_price = pyth::load::<pyth::Price>(&pyth_price_data)
1773        .map_err(|_| ProgramError::InvalidAccountData)?;
1774
1775    if pyth_price.ptype != pyth::PriceType::Price {
1776        msg!("Oracle price type is invalid");
1777        return Err(LendingError::InvalidOracleConfig.into());
1778    }
1779
1780    if pyth_price.agg.status != pyth::PriceStatus::Trading {
1781        msg!("Oracle price status is invalid");
1782        return Err(LendingError::InvalidOracleConfig.into());
1783    }
1784
1785    let slots_elapsed = clock
1786        .slot
1787        .checked_sub(pyth_price.valid_slot)
1788        .ok_or(LendingError::MathOverflow)?;
1789    if slots_elapsed >= STALE_AFTER_SLOTS_ELAPSED {
1790        msg!("Oracle price is stale");
1791        return Err(LendingError::InvalidOracleConfig.into());
1792    }
1793
1794    let price: u64 = pyth_price.agg.price.try_into().map_err(|_| {
1795        msg!("Oracle price cannot be negative");
1796        LendingError::InvalidOracleConfig
1797    })?;
1798
1799    let market_price = if pyth_price.expo >= 0 {
1800        let exponent = pyth_price
1801            .expo
1802            .try_into()
1803            .map_err(|_| LendingError::MathOverflow)?;
1804        let zeros = 10u64
1805            .checked_pow(exponent)
1806            .ok_or(LendingError::MathOverflow)?;
1807        Decimal::from(price).try_mul(zeros)?
1808    } else {
1809        let exponent = pyth_price
1810            .expo
1811            .checked_abs()
1812            .ok_or(LendingError::MathOverflow)?
1813            .try_into()
1814            .map_err(|_| LendingError::MathOverflow)?;
1815        let decimals = 10u64
1816            .checked_pow(exponent)
1817            .ok_or(LendingError::MathOverflow)?;
1818        Decimal::from(price).try_div(decimals)?
1819    };
1820
1821    Ok(market_price)
1822}
1823
1824/// Issue a spl_token `InitializeAccount` instruction.
1825#[inline(always)]
1826fn spl_token_init_account(params: TokenInitializeAccountParams<'_>) -> ProgramResult {
1827    let TokenInitializeAccountParams {
1828        account,
1829        mint,
1830        owner,
1831        rent,
1832        token_program,
1833    } = params;
1834    let ix = spl_token::instruction::initialize_account(
1835        token_program.key,
1836        account.key,
1837        mint.key,
1838        owner.key,
1839    )?;
1840    let result = invoke(&ix, &[account, mint, owner, rent, token_program]);
1841    result.map_err(|_| LendingError::TokenInitializeAccountFailed.into())
1842}
1843
1844/// Issue a spl_token `InitializeMint` instruction.
1845#[inline(always)]
1846fn spl_token_init_mint(params: TokenInitializeMintParams<'_, '_>) -> ProgramResult {
1847    let TokenInitializeMintParams {
1848        mint,
1849        rent,
1850        authority,
1851        token_program,
1852        decimals,
1853    } = params;
1854    let ix = spl_token::instruction::initialize_mint(
1855        token_program.key,
1856        mint.key,
1857        authority,
1858        None,
1859        decimals,
1860    )?;
1861    let result = invoke(&ix, &[mint, rent, token_program]);
1862    result.map_err(|_| LendingError::TokenInitializeMintFailed.into())
1863}
1864
1865/// Invoke signed unless signers seeds are empty
1866#[inline(always)]
1867fn invoke_optionally_signed(
1868    instruction: &Instruction,
1869    account_infos: &[AccountInfo],
1870    authority_signer_seeds: &[&[u8]],
1871) -> ProgramResult {
1872    if authority_signer_seeds.is_empty() {
1873        invoke(instruction, account_infos)
1874    } else {
1875        invoke_signed(instruction, account_infos, &[authority_signer_seeds])
1876    }
1877}
1878
1879/// Issue a spl_token `Transfer` instruction.
1880#[inline(always)]
1881fn spl_token_transfer(params: TokenTransferParams<'_, '_>) -> ProgramResult {
1882    let TokenTransferParams {
1883        source,
1884        destination,
1885        authority,
1886        token_program,
1887        amount,
1888        authority_signer_seeds,
1889    } = params;
1890    let result = invoke_optionally_signed(
1891        &spl_token::instruction::transfer(
1892            token_program.key,
1893            source.key,
1894            destination.key,
1895            authority.key,
1896            &[],
1897            amount,
1898        )?,
1899        &[source, destination, authority, token_program],
1900        authority_signer_seeds,
1901    );
1902    result.map_err(|_| LendingError::TokenTransferFailed.into())
1903}
1904
1905/// Issue a spl_token `MintTo` instruction.
1906fn spl_token_mint_to(params: TokenMintToParams<'_, '_>) -> ProgramResult {
1907    let TokenMintToParams {
1908        mint,
1909        destination,
1910        authority,
1911        token_program,
1912        amount,
1913        authority_signer_seeds,
1914    } = params;
1915    let result = invoke_optionally_signed(
1916        &spl_token::instruction::mint_to(
1917            token_program.key,
1918            mint.key,
1919            destination.key,
1920            authority.key,
1921            &[],
1922            amount,
1923        )?,
1924        &[mint, destination, authority, token_program],
1925        authority_signer_seeds,
1926    );
1927    result.map_err(|_| LendingError::TokenMintToFailed.into())
1928}
1929
1930/// Issue a spl_token `Burn` instruction.
1931#[inline(always)]
1932fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
1933    let TokenBurnParams {
1934        mint,
1935        source,
1936        authority,
1937        token_program,
1938        amount,
1939        authority_signer_seeds,
1940    } = params;
1941    let result = invoke_optionally_signed(
1942        &spl_token::instruction::burn(
1943            token_program.key,
1944            source.key,
1945            mint.key,
1946            authority.key,
1947            &[],
1948            amount,
1949        )?,
1950        &[source, mint, authority, token_program],
1951        authority_signer_seeds,
1952    );
1953    result.map_err(|_| LendingError::TokenBurnFailed.into())
1954}
1955
1956struct TokenInitializeMintParams<'a: 'b, 'b> {
1957    mint: AccountInfo<'a>,
1958    rent: AccountInfo<'a>,
1959    authority: &'b Pubkey,
1960    decimals: u8,
1961    token_program: AccountInfo<'a>,
1962}
1963
1964struct TokenInitializeAccountParams<'a> {
1965    account: AccountInfo<'a>,
1966    mint: AccountInfo<'a>,
1967    owner: AccountInfo<'a>,
1968    rent: AccountInfo<'a>,
1969    token_program: AccountInfo<'a>,
1970}
1971
1972struct TokenTransferParams<'a: 'b, 'b> {
1973    source: AccountInfo<'a>,
1974    destination: AccountInfo<'a>,
1975    amount: u64,
1976    authority: AccountInfo<'a>,
1977    authority_signer_seeds: &'b [&'b [u8]],
1978    token_program: AccountInfo<'a>,
1979}
1980
1981struct TokenMintToParams<'a: 'b, 'b> {
1982    mint: AccountInfo<'a>,
1983    destination: AccountInfo<'a>,
1984    amount: u64,
1985    authority: AccountInfo<'a>,
1986    authority_signer_seeds: &'b [&'b [u8]],
1987    token_program: AccountInfo<'a>,
1988}
1989
1990struct TokenBurnParams<'a: 'b, 'b> {
1991    mint: AccountInfo<'a>,
1992    source: AccountInfo<'a>,
1993    amount: u64,
1994    authority: AccountInfo<'a>,
1995    authority_signer_seeds: &'b [&'b [u8]],
1996    token_program: AccountInfo<'a>,
1997}
1998
1999impl PrintProgramError for LendingError {
2000    fn print<E>(&self)
2001    where
2002        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
2003    {
2004        msg!(&self.to_string());
2005    }
2006}