solana-program-library/token-lending/program/src/processor.rs

1381 lines
54 KiB
Rust

//! Program state processor
use crate::{
dex_market::{DexMarket, TradeSimulator, BASE_MINT_OFFSET, QUOTE_MINT_OFFSET},
error::LendingError,
instruction::{BorrowAmountType, LendingInstruction},
math::{Decimal, TryAdd, WAD},
state::{
LendingMarket, LiquidateResult, NewObligationParams, NewReserveParams, Obligation,
RepayResult, Reserve, ReserveCollateral, ReserveConfig, ReserveLiquidity, PROGRAM_VERSION,
},
};
use num_traits::FromPrimitive;
use solana_program::{
account_info::{next_account_info, AccountInfo},
clock::Slot,
decode_error::DecodeError,
entrypoint::ProgramResult,
msg,
program::{invoke, invoke_signed},
program_error::{PrintProgramError, ProgramError},
program_option::COption,
program_pack::{IsInitialized, Pack},
pubkey::Pubkey,
sysvar::{clock::Clock, rent::Rent, Sysvar},
};
use spl_token::state::Account as Token;
/// Processes an instruction
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
let instruction = LendingInstruction::unpack(input)?;
match instruction {
LendingInstruction::InitLendingMarket { market_owner } => {
msg!("Instruction: Init Lending Market");
process_init_lending_market(program_id, market_owner, accounts)
}
LendingInstruction::InitReserve {
liquidity_amount,
config,
} => {
msg!("Instruction: Init Reserve");
process_init_reserve(program_id, liquidity_amount, config, accounts)
}
LendingInstruction::InitObligation => {
msg!("Instruction: Init Obligation");
process_init_obligation(program_id, accounts)
}
LendingInstruction::DepositReserveLiquidity { liquidity_amount } => {
msg!("Instruction: Deposit");
process_deposit(program_id, liquidity_amount, accounts)
}
LendingInstruction::WithdrawReserveLiquidity { collateral_amount } => {
msg!("Instruction: Withdraw");
process_withdraw(program_id, collateral_amount, accounts)
}
LendingInstruction::BorrowReserveLiquidity {
amount,
amount_type,
} => {
msg!("Instruction: Borrow");
process_borrow(program_id, amount, amount_type, accounts)
}
LendingInstruction::RepayReserveLiquidity { liquidity_amount } => {
msg!("Instruction: Repay");
process_repay(program_id, liquidity_amount, accounts)
}
LendingInstruction::LiquidateObligation { liquidity_amount } => {
msg!("Instruction: Liquidate");
process_liquidate(program_id, liquidity_amount, accounts)
}
LendingInstruction::AccrueReserveInterest => {
msg!("Instruction: Accrue Interest");
process_accrue_interest(program_id, accounts)
}
}
}
fn process_init_lending_market(
_program_id: &Pubkey,
market_owner: Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let lending_market_info = next_account_info(account_info_iter)?;
let quote_token_mint_info = next_account_info(account_info_iter)?;
let rent = &Rent::from_account_info(next_account_info(account_info_iter)?)?;
let token_program_id = next_account_info(account_info_iter)?;
unpack_mint(&quote_token_mint_info.data.borrow())?;
if quote_token_mint_info.owner != token_program_id.key {
return Err(LendingError::InvalidTokenOwner.into());
}
assert_rent_exempt(rent, lending_market_info)?;
let mut new_lending_market: LendingMarket = assert_uninitialized(lending_market_info)?;
new_lending_market.version = PROGRAM_VERSION;
new_lending_market.owner = market_owner;
new_lending_market.quote_token_mint = *quote_token_mint_info.key;
new_lending_market.token_program_id = *token_program_id.key;
LendingMarket::pack(
new_lending_market,
&mut lending_market_info.data.borrow_mut(),
)?;
Ok(())
}
fn process_init_reserve(
program_id: &Pubkey,
liquidity_amount: u64,
config: ReserveConfig,
accounts: &[AccountInfo],
) -> ProgramResult {
if liquidity_amount == 0 {
msg!("Reserve must be initialized with liquidity");
return Err(LendingError::InvalidAmount.into());
}
if config.optimal_utilization_rate > 100 {
msg!("Optimal utilization rate must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.loan_to_value_ratio >= 100 {
msg!("Loan to value ratio must be in range [0, 100)");
return Err(LendingError::InvalidConfig.into());
}
if config.liquidation_bonus > 100 {
msg!("Liquidation bonus must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.liquidation_threshold <= config.loan_to_value_ratio
|| config.liquidation_threshold > 100
{
msg!("Liquidation threshold must be in range (LTV, 100]");
return Err(LendingError::InvalidConfig.into());
}
if config.optimal_borrow_rate < config.min_borrow_rate {
msg!("Optimal borrow rate must be >= min borrow rate");
return Err(LendingError::InvalidConfig.into());
}
if config.optimal_borrow_rate > config.max_borrow_rate {
msg!("Optimal borrow rate must be <= max borrow rate");
return Err(LendingError::InvalidConfig.into());
}
if config.fees.borrow_fee_wad >= WAD {
msg!("Borrow fee must be in range [0, 1_000_000_000_000_000_000)");
return Err(LendingError::InvalidConfig.into());
}
if config.fees.host_fee_percentage > 100 {
msg!("Host fee percentage must be in range [0, 100]");
return Err(LendingError::InvalidConfig.into());
}
let account_info_iter = &mut accounts.iter();
let source_liquidity_info = next_account_info(account_info_iter)?;
let destination_collateral_info = next_account_info(account_info_iter)?;
let reserve_info = next_account_info(account_info_iter)?;
let reserve_liquidity_mint_info = next_account_info(account_info_iter)?;
let reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
let reserve_collateral_mint_info = next_account_info(account_info_iter)?;
let reserve_collateral_supply_info = next_account_info(account_info_iter)?;
let reserve_collateral_fees_receiver_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let lending_market_owner_info = next_account_info(account_info_iter)?;
let lending_market_authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
let rent_info = next_account_info(account_info_iter)?;
let rent = &Rent::from_account_info(rent_info)?;
let token_program_id = next_account_info(account_info_iter)?;
if reserve_liquidity_supply_info.key == source_liquidity_info.key {
msg!("Cannot use reserve liquidity supply as source account input");
return Err(LendingError::InvalidAccountInput.into());
}
assert_rent_exempt(rent, reserve_info)?;
assert_uninitialized::<Reserve>(reserve_info)?;
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
return Err(LendingError::InvalidTokenProgram.into());
}
if &lending_market.owner != lending_market_owner_info.key {
return Err(LendingError::InvalidMarketOwner.into());
}
if !lending_market_owner_info.is_signer {
return Err(LendingError::InvalidSigner.into());
}
let dex_market = if reserve_liquidity_mint_info.key != &lending_market.quote_token_mint {
let dex_market_info = next_account_info(account_info_iter)?;
// TODO: check that market state is owned by real serum dex program
if !rent.is_exempt(dex_market_info.lamports(), dex_market_info.data_len()) {
return Err(LendingError::NotRentExempt.into());
}
let dex_market_data = &dex_market_info.data.borrow();
let market_quote_mint = DexMarket::pubkey_at_offset(&dex_market_data, QUOTE_MINT_OFFSET);
if lending_market.quote_token_mint != market_quote_mint {
return Err(LendingError::DexMarketMintMismatch.into());
}
let market_base_mint = DexMarket::pubkey_at_offset(&dex_market_data, BASE_MINT_OFFSET);
if reserve_liquidity_mint_info.key != &market_base_mint {
return Err(LendingError::DexMarketMintMismatch.into());
}
COption::Some(*dex_market_info.key)
} else {
COption::None
};
let authority_signer_seeds = &[
lending_market_info.key.as_ref(),
&[lending_market.bump_seed],
];
let lending_market_authority_pubkey =
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
if lending_market_authority_info.key != &lending_market_authority_pubkey {
return Err(LendingError::InvalidMarketAuthority.into());
}
let reserve_liquidity_mint = unpack_mint(&reserve_liquidity_mint_info.data.borrow())?;
if reserve_liquidity_mint_info.owner != token_program_id.key {
return Err(LendingError::InvalidTokenOwner.into());
}
let reserve_liquidity_info = ReserveLiquidity::new(
*reserve_liquidity_mint_info.key,
reserve_liquidity_mint.decimals,
*reserve_liquidity_supply_info.key,
);
let reserve_collateral_info = ReserveCollateral::new(
*reserve_collateral_mint_info.key,
*reserve_collateral_supply_info.key,
*reserve_collateral_fees_receiver_info.key,
);
let mut reserve = Reserve::new(NewReserveParams {
current_slot: clock.slot,
lending_market: *lending_market_info.key,
collateral: reserve_collateral_info,
liquidity: reserve_liquidity_info,
dex_market,
config,
});
let collateral_amount = reserve.deposit_liquidity(liquidity_amount)?;
Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
spl_token_init_account(TokenInitializeAccountParams {
account: reserve_liquidity_supply_info.clone(),
mint: reserve_liquidity_mint_info.clone(),
owner: lending_market_authority_info.clone(),
rent: rent_info.clone(),
token_program: token_program_id.clone(),
})?;
spl_token_init_mint(TokenInitializeMintParams {
mint: reserve_collateral_mint_info.clone(),
authority: lending_market_authority_info.key,
rent: rent_info.clone(),
decimals: reserve_liquidity_mint.decimals,
token_program: token_program_id.clone(),
})?;
spl_token_init_account(TokenInitializeAccountParams {
account: reserve_collateral_supply_info.clone(),
mint: reserve_collateral_mint_info.clone(),
owner: lending_market_authority_info.clone(),
rent: rent_info.clone(),
token_program: token_program_id.clone(),
})?;
spl_token_init_account(TokenInitializeAccountParams {
account: reserve_collateral_fees_receiver_info.clone(),
mint: reserve_collateral_mint_info.clone(),
owner: lending_market_owner_info.clone(),
rent: rent_info.clone(),
token_program: token_program_id.clone(),
})?;
spl_token_init_account(TokenInitializeAccountParams {
account: destination_collateral_info.clone(),
mint: reserve_collateral_mint_info.clone(),
owner: lending_market_authority_info.clone(),
rent: rent_info.clone(),
token_program: token_program_id.clone(),
})?;
spl_token_transfer(TokenTransferParams {
source: source_liquidity_info.clone(),
destination: reserve_liquidity_supply_info.clone(),
amount: liquidity_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
spl_token_mint_to(TokenMintToParams {
mint: reserve_collateral_mint_info.clone(),
destination: destination_collateral_info.clone(),
amount: collateral_amount,
authority: lending_market_authority_info.clone(),
authority_signer_seeds,
token_program: token_program_id.clone(),
})?;
Ok(())
}
#[inline(never)] // avoid stack frame limit
fn process_init_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let deposit_reserve_info = next_account_info(account_info_iter)?;
let borrow_reserve_info = next_account_info(account_info_iter)?;
let obligation_info = next_account_info(account_info_iter)?;
let obligation_token_mint_info = next_account_info(account_info_iter)?;
let obligation_token_output_info = next_account_info(account_info_iter)?;
let obligation_token_owner_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let lending_market_authority_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
let rent_info = next_account_info(account_info_iter)?;
let rent = &Rent::from_account_info(rent_info)?;
let token_program_id = next_account_info(account_info_iter)?;
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
return Err(LendingError::InvalidTokenProgram.into());
}
let deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?;
if deposit_reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &deposit_reserve.lending_market != lending_market_info.key {
msg!("Invalid reserve lending market account");
return Err(LendingError::InvalidAccountInput.into());
}
let borrow_reserve = Reserve::unpack(&borrow_reserve_info.data.borrow())?;
if borrow_reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if borrow_reserve.lending_market != deposit_reserve.lending_market {
return Err(LendingError::LendingMarketMismatch.into());
}
if deposit_reserve.config.loan_to_value_ratio == 0 {
return Err(LendingError::ReserveCollateralDisabled.into());
}
if deposit_reserve_info.key == borrow_reserve_info.key {
return Err(LendingError::DuplicateReserve.into());
}
if deposit_reserve.liquidity.mint_pubkey == borrow_reserve.liquidity.mint_pubkey {
return Err(LendingError::DuplicateReserveMint.into());
}
assert_rent_exempt(rent, obligation_info)?;
assert_uninitialized::<Obligation>(obligation_info)?;
assert_last_update_slot(&borrow_reserve, clock.slot)?;
let cumulative_borrow_rate = borrow_reserve.cumulative_borrow_rate_wads;
let obligation_mint_decimals = deposit_reserve.liquidity.mint_decimals;
let obligation = Obligation::new(NewObligationParams {
collateral_reserve: *deposit_reserve_info.key,
cumulative_borrow_rate_wads: cumulative_borrow_rate,
borrow_reserve: *borrow_reserve_info.key,
token_mint: *obligation_token_mint_info.key,
});
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
let authority_signer_seeds = &[
lending_market_info.key.as_ref(),
&[lending_market.bump_seed],
];
let lending_market_authority_pubkey =
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
if lending_market_authority_info.key != &lending_market_authority_pubkey {
return Err(LendingError::InvalidMarketAuthority.into());
}
// init obligation token mint
spl_token_init_mint(TokenInitializeMintParams {
mint: obligation_token_mint_info.clone(),
authority: lending_market_authority_info.key,
rent: rent_info.clone(),
decimals: obligation_mint_decimals,
token_program: token_program_id.clone(),
})?;
// init obligation token output account
spl_token_init_account(TokenInitializeAccountParams {
account: obligation_token_output_info.clone(),
mint: obligation_token_mint_info.clone(),
owner: obligation_token_owner_info.clone(),
rent: rent_info.clone(),
token_program: token_program_id.clone(),
})?;
Ok(())
}
fn process_deposit(
program_id: &Pubkey,
liquidity_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
if liquidity_amount == 0 {
return Err(LendingError::InvalidAmount.into());
}
let account_info_iter = &mut accounts.iter();
let source_liquidity_info = next_account_info(account_info_iter)?;
let destination_collateral_info = next_account_info(account_info_iter)?;
let reserve_info = next_account_info(account_info_iter)?;
let reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
let reserve_collateral_mint_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let lending_market_authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
let token_program_id = next_account_info(account_info_iter)?;
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
return Err(LendingError::InvalidTokenProgram.into());
}
let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
if reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &reserve.lending_market != lending_market_info.key {
msg!("Invalid reserve lending market account");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.liquidity.supply_pubkey != reserve_liquidity_supply_info.key {
msg!("Invalid reserve liquidity supply account");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.collateral.mint_pubkey != reserve_collateral_mint_info.key {
msg!("Invalid reserve collateral mint account");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.liquidity.supply_pubkey == source_liquidity_info.key {
msg!("Cannot use reserve liquidity supply as source account input");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.collateral.supply_pubkey == destination_collateral_info.key {
msg!("Cannot use reserve collateral supply as destination account input");
return Err(LendingError::InvalidAccountInput.into());
}
assert_last_update_slot(&reserve, clock.slot)?;
let collateral_amount = reserve.deposit_liquidity(liquidity_amount)?;
Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
let authority_signer_seeds = &[
lending_market_info.key.as_ref(),
&[lending_market.bump_seed],
];
let lending_market_authority_pubkey =
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
if lending_market_authority_info.key != &lending_market_authority_pubkey {
return Err(LendingError::InvalidMarketAuthority.into());
}
spl_token_transfer(TokenTransferParams {
source: source_liquidity_info.clone(),
destination: reserve_liquidity_supply_info.clone(),
amount: liquidity_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
spl_token_mint_to(TokenMintToParams {
mint: reserve_collateral_mint_info.clone(),
destination: destination_collateral_info.clone(),
amount: collateral_amount,
authority: lending_market_authority_info.clone(),
authority_signer_seeds,
token_program: token_program_id.clone(),
})?;
Ok(())
}
fn process_withdraw(
program_id: &Pubkey,
collateral_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
if collateral_amount == 0 {
return Err(LendingError::InvalidAmount.into());
}
let account_info_iter = &mut accounts.iter();
let source_collateral_info = next_account_info(account_info_iter)?;
let destination_liquidity_info = next_account_info(account_info_iter)?;
let reserve_info = next_account_info(account_info_iter)?;
let reserve_collateral_mint_info = next_account_info(account_info_iter)?;
let reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let lending_market_authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
let token_program_id = next_account_info(account_info_iter)?;
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
return Err(LendingError::InvalidTokenProgram.into());
}
let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
if reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &reserve.lending_market != lending_market_info.key {
msg!("Invalid reserve lending market account");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.liquidity.supply_pubkey != reserve_liquidity_supply_info.key {
msg!("Invalid reserve liquidity supply account");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.collateral.mint_pubkey != reserve_collateral_mint_info.key {
msg!("Invalid reserve collateral mint account");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.liquidity.supply_pubkey == destination_liquidity_info.key {
msg!("Cannot use reserve liquidity supply as destination account input");
return Err(LendingError::InvalidAccountInput.into());
}
if &reserve.collateral.supply_pubkey == source_collateral_info.key {
msg!("Cannot use reserve collateral supply as source account input");
return Err(LendingError::InvalidAccountInput.into());
}
assert_last_update_slot(&reserve, clock.slot)?;
let liquidity_withdraw_amount = reserve.redeem_collateral(collateral_amount)?;
Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
let authority_signer_seeds = &[
lending_market_info.key.as_ref(),
&[lending_market.bump_seed],
];
let lending_market_authority_pubkey =
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
if lending_market_authority_info.key != &lending_market_authority_pubkey {
return Err(LendingError::InvalidMarketAuthority.into());
}
spl_token_transfer(TokenTransferParams {
source: reserve_liquidity_supply_info.clone(),
destination: destination_liquidity_info.clone(),
amount: liquidity_withdraw_amount,
authority: lending_market_authority_info.clone(),
authority_signer_seeds,
token_program: token_program_id.clone(),
})?;
spl_token_burn(TokenBurnParams {
mint: reserve_collateral_mint_info.clone(),
source: source_collateral_info.clone(),
amount: collateral_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
Ok(())
}
#[inline(never)] // avoid stack frame limit
fn process_borrow(
program_id: &Pubkey,
token_amount: u64,
token_amount_type: BorrowAmountType,
accounts: &[AccountInfo],
) -> ProgramResult {
if token_amount == 0 {
return Err(LendingError::InvalidAmount.into());
}
let account_info_iter = &mut accounts.iter();
let source_collateral_info = next_account_info(account_info_iter)?;
let destination_liquidity_info = next_account_info(account_info_iter)?;
let deposit_reserve_info = next_account_info(account_info_iter)?;
let deposit_reserve_collateral_supply_info = next_account_info(account_info_iter)?;
let deposit_reserve_collateral_fees_receiver_info = next_account_info(account_info_iter)?;
let borrow_reserve_info = next_account_info(account_info_iter)?;
let borrow_reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
let obligation_info = next_account_info(account_info_iter)?;
let obligation_token_mint_info = next_account_info(account_info_iter)?;
let obligation_token_output_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let lending_market_authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let dex_market_info = next_account_info(account_info_iter)?;
let dex_market_orders_info = next_account_info(account_info_iter)?;
let memory = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
let token_program_id = next_account_info(account_info_iter)?;
// Ensure memory is owned by this program so that we don't have to zero it out
if memory.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
return Err(LendingError::InvalidTokenProgram.into());
}
let deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?;
if deposit_reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &deposit_reserve.lending_market != lending_market_info.key {
msg!("Invalid reserve lending market account");
return Err(LendingError::InvalidAccountInput.into());
}
let mut borrow_reserve = Reserve::unpack(&borrow_reserve_info.data.borrow())?;
if borrow_reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if borrow_reserve.lending_market != deposit_reserve.lending_market {
return Err(LendingError::LendingMarketMismatch.into());
}
if deposit_reserve.config.loan_to_value_ratio == 0 {
return Err(LendingError::ReserveCollateralDisabled.into());
}
if deposit_reserve_info.key == borrow_reserve_info.key {
return Err(LendingError::DuplicateReserve.into());
}
if deposit_reserve.liquidity.mint_pubkey == borrow_reserve.liquidity.mint_pubkey {
return Err(LendingError::DuplicateReserveMint.into());
}
if &borrow_reserve.liquidity.supply_pubkey != borrow_reserve_liquidity_supply_info.key {
msg!("Invalid borrow reserve liquidity supply account input");
return Err(LendingError::InvalidAccountInput.into());
}
if &deposit_reserve.collateral.supply_pubkey != deposit_reserve_collateral_supply_info.key {
msg!("Invalid deposit reserve collateral supply account input");
return Err(LendingError::InvalidAccountInput.into());
}
if &deposit_reserve.collateral.supply_pubkey == source_collateral_info.key {
msg!("Cannot use deposit reserve collateral supply as source account input");
return Err(LendingError::InvalidAccountInput.into());
}
if &deposit_reserve.collateral.fees_receiver
!= deposit_reserve_collateral_fees_receiver_info.key
{
msg!("Invalid deposit reserve collateral fees receiver account");
return Err(LendingError::InvalidAccountInput.into());
}
if &borrow_reserve.liquidity.supply_pubkey == destination_liquidity_info.key {
msg!("Cannot use borrow reserve liquidity supply as destination account input");
return Err(LendingError::InvalidAccountInput.into());
}
// TODO: handle case when neither reserve is the quote currency
if borrow_reserve.dex_market.is_none() && deposit_reserve.dex_market.is_none() {
msg!("One reserve must have a dex market");
return Err(LendingError::InvalidAccountInput.into());
}
if let COption::Some(dex_market_pubkey) = borrow_reserve.dex_market {
if &dex_market_pubkey != dex_market_info.key {
msg!("Invalid dex market account input");
return Err(LendingError::InvalidAccountInput.into());
}
}
if let COption::Some(dex_market_pubkey) = deposit_reserve.dex_market {
if &dex_market_pubkey != dex_market_info.key {
msg!("Invalid dex market account input");
return Err(LendingError::InvalidAccountInput.into());
}
}
let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
if obligation_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &obligation.borrow_reserve != borrow_reserve_info.key {
msg!("Borrow reserve input doesn't match existing obligation borrow reserve");
return Err(LendingError::InvalidAccountInput.into());
}
if &obligation.collateral_reserve != deposit_reserve_info.key {
msg!("Collateral reserve input doesn't match existing obligation collateral reserve");
return Err(LendingError::InvalidAccountInput.into());
}
unpack_mint(&obligation_token_mint_info.data.borrow())?;
if &obligation.token_mint != obligation_token_mint_info.key {
msg!("Obligation token mint input doesn't match existing obligation token mint");
return Err(LendingError::InvalidTokenMint.into());
}
let obligation_token_output = Token::unpack(&obligation_token_output_info.data.borrow())?;
if obligation_token_output_info.owner != token_program_id.key {
return Err(LendingError::InvalidTokenOwner.into());
}
if &obligation_token_output.mint != obligation_token_mint_info.key {
return Err(LendingError::InvalidTokenMint.into());
}
assert_last_update_slot(&borrow_reserve, clock.slot)?;
assert_last_update_slot(&deposit_reserve, clock.slot)?;
obligation.accrue_interest(borrow_reserve.cumulative_borrow_rate_wads)?;
let trade_simulator = TradeSimulator::new(
dex_market_info,
dex_market_orders_info,
memory,
&lending_market.quote_token_mint,
&borrow_reserve.liquidity.mint_pubkey,
&deposit_reserve.liquidity.mint_pubkey,
)?;
let loan = deposit_reserve.create_loan(
token_amount,
token_amount_type,
trade_simulator,
&borrow_reserve.liquidity.mint_pubkey,
)?;
borrow_reserve.liquidity.borrow(loan.borrow_amount)?;
obligation.borrowed_liquidity_wads = obligation
.borrowed_liquidity_wads
.try_add(Decimal::from(loan.borrow_amount))?;
obligation.deposited_collateral_tokens += loan.collateral_amount;
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
Reserve::pack(borrow_reserve, &mut borrow_reserve_info.data.borrow_mut())?;
let authority_signer_seeds = &[
lending_market_info.key.as_ref(),
&[lending_market.bump_seed],
];
let lending_market_authority_pubkey =
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
if lending_market_authority_info.key != &lending_market_authority_pubkey {
return Err(LendingError::InvalidMarketAuthority.into());
}
// deposit collateral
spl_token_transfer(TokenTransferParams {
source: source_collateral_info.clone(),
destination: deposit_reserve_collateral_supply_info.clone(),
amount: loan.collateral_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
// transfer host fees if host is specified
let mut owner_fee = loan.origination_fee;
if let Ok(host_fee_recipient) = next_account_info(account_info_iter) {
if loan.host_fee > 0 {
owner_fee -= loan.host_fee;
spl_token_transfer(TokenTransferParams {
source: source_collateral_info.clone(),
destination: host_fee_recipient.clone(),
amount: loan.host_fee,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
}
}
// transfer remaining fees to owner
if owner_fee > 0 {
spl_token_transfer(TokenTransferParams {
source: source_collateral_info.clone(),
destination: deposit_reserve_collateral_fees_receiver_info.clone(),
amount: owner_fee,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
}
// borrow liquidity
spl_token_transfer(TokenTransferParams {
source: borrow_reserve_liquidity_supply_info.clone(),
destination: destination_liquidity_info.clone(),
amount: loan.borrow_amount,
authority: lending_market_authority_info.clone(),
authority_signer_seeds,
token_program: token_program_id.clone(),
})?;
// mint obligation tokens to output account
spl_token_mint_to(TokenMintToParams {
mint: obligation_token_mint_info.clone(),
destination: obligation_token_output_info.clone(),
amount: loan.collateral_amount,
authority: lending_market_authority_info.clone(),
authority_signer_seeds,
token_program: token_program_id.clone(),
})?;
Ok(())
}
#[inline(never)] // avoid stack frame limit
fn process_repay(
program_id: &Pubkey,
liquidity_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
if liquidity_amount == 0 {
return Err(LendingError::InvalidAmount.into());
}
let account_info_iter = &mut accounts.iter();
let source_liquidity_info = next_account_info(account_info_iter)?;
let destination_collateral_info = next_account_info(account_info_iter)?;
let repay_reserve_info = next_account_info(account_info_iter)?;
let repay_reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
let withdraw_reserve_info = next_account_info(account_info_iter)?;
let withdraw_reserve_collateral_supply_info = next_account_info(account_info_iter)?;
let obligation_info = next_account_info(account_info_iter)?;
let obligation_token_mint_info = next_account_info(account_info_iter)?;
let obligation_token_input_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let lending_market_authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
let token_program_id = next_account_info(account_info_iter)?;
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
return Err(LendingError::InvalidTokenProgram.into());
}
let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
if obligation_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &obligation.borrow_reserve != repay_reserve_info.key {
msg!("Invalid repay reserve account");
return Err(LendingError::InvalidAccountInput.into());
}
if &obligation.collateral_reserve != withdraw_reserve_info.key {
msg!("Invalid withdraw reserve account");
return Err(LendingError::InvalidAccountInput.into());
}
if obligation.deposited_collateral_tokens == 0 {
return Err(LendingError::ObligationEmpty.into());
}
let obligation_mint = unpack_mint(&obligation_token_mint_info.data.borrow())?;
if &obligation.token_mint != obligation_token_mint_info.key {
msg!("Invalid obligation token mint account");
return Err(LendingError::InvalidAccountInput.into());
}
let mut repay_reserve = Reserve::unpack(&repay_reserve_info.data.borrow())?;
if repay_reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &repay_reserve.lending_market != lending_market_info.key {
msg!("Invalid reserve lending market account");
return Err(LendingError::InvalidAccountInput.into());
}
let withdraw_reserve = Reserve::unpack(&withdraw_reserve_info.data.borrow())?;
if withdraw_reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if withdraw_reserve.lending_market != repay_reserve.lending_market {
return Err(LendingError::LendingMarketMismatch.into());
}
if repay_reserve_info.key == withdraw_reserve_info.key {
return Err(LendingError::DuplicateReserve.into());
}
if repay_reserve.liquidity.mint_pubkey == withdraw_reserve.liquidity.mint_pubkey {
return Err(LendingError::DuplicateReserveMint.into());
}
if &repay_reserve.liquidity.supply_pubkey != repay_reserve_liquidity_supply_info.key {
msg!("Invalid repay reserve liquidity supply account");
return Err(LendingError::InvalidAccountInput.into());
}
if &withdraw_reserve.collateral.supply_pubkey != withdraw_reserve_collateral_supply_info.key {
msg!("Invalid withdraw reserve collateral supply account");
return Err(LendingError::InvalidAccountInput.into());
}
if &repay_reserve.liquidity.supply_pubkey == source_liquidity_info.key {
msg!("Cannot use repay reserve liquidity supply as source account input");
return Err(LendingError::InvalidAccountInput.into());
}
if &withdraw_reserve.collateral.supply_pubkey == destination_collateral_info.key {
msg!("Cannot use withdraw reserve collateral supply as destination account input");
return Err(LendingError::InvalidAccountInput.into());
}
// accrue interest and update rates
assert_last_update_slot(&repay_reserve, clock.slot)?;
obligation.accrue_interest(repay_reserve.cumulative_borrow_rate_wads)?;
let RepayResult {
integer_repay_amount,
decimal_repay_amount,
collateral_withdraw_amount,
obligation_token_amount,
} = obligation.repay(liquidity_amount, obligation_mint.supply)?;
repay_reserve
.liquidity
.repay(integer_repay_amount, decimal_repay_amount)?;
Reserve::pack(repay_reserve, &mut repay_reserve_info.data.borrow_mut())?;
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
let authority_signer_seeds = &[
lending_market_info.key.as_ref(),
&[lending_market.bump_seed],
];
let lending_market_authority_pubkey =
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
if lending_market_authority_info.key != &lending_market_authority_pubkey {
return Err(LendingError::InvalidMarketAuthority.into());
}
// burn obligation tokens
spl_token_burn(TokenBurnParams {
mint: obligation_token_mint_info.clone(),
source: obligation_token_input_info.clone(),
amount: obligation_token_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
// deposit repaid liquidity
spl_token_transfer(TokenTransferParams {
source: source_liquidity_info.clone(),
destination: repay_reserve_liquidity_supply_info.clone(),
amount: integer_repay_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
// withdraw collateral
spl_token_transfer(TokenTransferParams {
source: withdraw_reserve_collateral_supply_info.clone(),
destination: destination_collateral_info.clone(),
amount: collateral_withdraw_amount,
authority: lending_market_authority_info.clone(),
authority_signer_seeds,
token_program: token_program_id.clone(),
})?;
Ok(())
}
#[inline(never)] // avoid stack frame limit
fn process_liquidate(
program_id: &Pubkey,
liquidity_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
if liquidity_amount == 0 {
return Err(LendingError::InvalidAmount.into());
}
let account_info_iter = &mut accounts.iter();
let source_liquidity_info = next_account_info(account_info_iter)?;
let destination_collateral_info = next_account_info(account_info_iter)?;
let repay_reserve_info = next_account_info(account_info_iter)?;
let repay_reserve_liquidity_supply_info = next_account_info(account_info_iter)?;
let withdraw_reserve_info = next_account_info(account_info_iter)?;
let withdraw_reserve_collateral_supply_info = next_account_info(account_info_iter)?;
let obligation_info = next_account_info(account_info_iter)?;
let lending_market_info = next_account_info(account_info_iter)?;
let lending_market_authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let dex_market_info = next_account_info(account_info_iter)?;
let dex_market_orders_info = next_account_info(account_info_iter)?;
let memory = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
let token_program_id = next_account_info(account_info_iter)?;
// Ensure memory is owned by this program so that we don't have to zero it out
if memory.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
return Err(LendingError::InvalidTokenProgram.into());
}
let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
if obligation_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &obligation.borrow_reserve != repay_reserve_info.key {
msg!("Invalid repay reserve account");
return Err(LendingError::InvalidAccountInput.into());
}
if &obligation.collateral_reserve != withdraw_reserve_info.key {
msg!("Invalid withdraw reserve account");
return Err(LendingError::InvalidAccountInput.into());
}
if obligation.deposited_collateral_tokens == 0 {
return Err(LendingError::ObligationEmpty.into());
}
let mut repay_reserve = Reserve::unpack(&repay_reserve_info.data.borrow())?;
if repay_reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if &repay_reserve.lending_market != lending_market_info.key {
msg!("Invalid reserve lending market account");
return Err(LendingError::InvalidAccountInput.into());
}
let withdraw_reserve = Reserve::unpack(&withdraw_reserve_info.data.borrow())?;
if withdraw_reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
if withdraw_reserve.lending_market != repay_reserve.lending_market {
return Err(LendingError::LendingMarketMismatch.into());
}
if repay_reserve_info.key == withdraw_reserve_info.key {
return Err(LendingError::DuplicateReserve.into());
}
if repay_reserve.liquidity.mint_pubkey == withdraw_reserve.liquidity.mint_pubkey {
return Err(LendingError::DuplicateReserveMint.into());
}
if &repay_reserve.liquidity.supply_pubkey != repay_reserve_liquidity_supply_info.key {
msg!("Invalid repay reserve liquidity supply account");
return Err(LendingError::InvalidAccountInput.into());
}
if &withdraw_reserve.collateral.supply_pubkey != withdraw_reserve_collateral_supply_info.key {
msg!("Invalid withdraw reserve collateral supply account");
return Err(LendingError::InvalidAccountInput.into());
}
if &repay_reserve.liquidity.supply_pubkey == source_liquidity_info.key {
msg!("Cannot use repay reserve liquidity supply as source account input");
return Err(LendingError::InvalidAccountInput.into());
}
if &withdraw_reserve.collateral.supply_pubkey == destination_collateral_info.key {
msg!("Cannot use withdraw reserve collateral supply as destination account input");
return Err(LendingError::InvalidAccountInput.into());
}
// TODO: handle case when neither reserve is the quote currency
if repay_reserve.dex_market.is_none() && withdraw_reserve.dex_market.is_none() {
msg!("One reserve must have a dex market");
return Err(LendingError::InvalidAccountInput.into());
}
if let COption::Some(dex_market_pubkey) = repay_reserve.dex_market {
if &dex_market_pubkey != dex_market_info.key {
msg!("Invalid dex market account");
return Err(LendingError::InvalidAccountInput.into());
}
}
if let COption::Some(dex_market_pubkey) = withdraw_reserve.dex_market {
if &dex_market_pubkey != dex_market_info.key {
msg!("Invalid dex market account");
return Err(LendingError::InvalidAccountInput.into());
}
}
// accrue interest and update rates
assert_last_update_slot(&repay_reserve, clock.slot)?;
assert_last_update_slot(&withdraw_reserve, clock.slot)?;
obligation.accrue_interest(repay_reserve.cumulative_borrow_rate_wads)?;
let trade_simulator = TradeSimulator::new(
dex_market_info,
dex_market_orders_info,
memory,
&lending_market.quote_token_mint,
&withdraw_reserve.liquidity.mint_pubkey,
&repay_reserve.liquidity.mint_pubkey,
)?;
let LiquidateResult {
withdraw_amount,
repay_amount,
settle_amount,
} = withdraw_reserve.liquidate_obligation(
&obligation,
liquidity_amount,
&repay_reserve.liquidity.mint_pubkey,
trade_simulator,
)?;
repay_reserve.liquidity.repay(repay_amount, settle_amount)?;
Reserve::pack(repay_reserve, &mut repay_reserve_info.data.borrow_mut())?;
obligation.liquidate(settle_amount, withdraw_amount)?;
Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
let authority_signer_seeds = &[
lending_market_info.key.as_ref(),
&[lending_market.bump_seed],
];
let lending_market_authority_pubkey =
Pubkey::create_program_address(authority_signer_seeds, program_id)?;
if lending_market_authority_info.key != &lending_market_authority_pubkey {
return Err(LendingError::InvalidMarketAuthority.into());
}
// deposit repaid liquidity
spl_token_transfer(TokenTransferParams {
source: source_liquidity_info.clone(),
destination: repay_reserve_liquidity_supply_info.clone(),
amount: repay_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;
// withdraw collateral
spl_token_transfer(TokenTransferParams {
source: withdraw_reserve_collateral_supply_info.clone(),
destination: destination_collateral_info.clone(),
amount: withdraw_amount,
authority: lending_market_authority_info.clone(),
authority_signer_seeds,
token_program: token_program_id.clone(),
})?;
Ok(())
}
#[inline(never)] // avoid stack frame limit
fn process_accrue_interest(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let clock = &Clock::from_account_info(next_account_info(account_info_iter)?)?;
for reserve_info in account_info_iter {
let mut reserve = Reserve::unpack(&reserve_info.data.borrow())?;
if reserve_info.owner != program_id {
return Err(LendingError::InvalidAccountOwner.into());
}
reserve.accrue_interest(clock.slot)?;
Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?;
}
Ok(())
}
fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
if !rent.is_exempt(account_info.lamports(), account_info.data_len()) {
msg!(&rent.minimum_balance(account_info.data_len()).to_string());
Err(LendingError::NotRentExempt.into())
} else {
Ok(())
}
}
fn assert_last_update_slot(reserve: &Reserve, slot: Slot) -> ProgramResult {
if !reserve.last_update_slot == slot {
Err(LendingError::ReserveStale.into())
} else {
Ok(())
}
}
fn assert_uninitialized<T: Pack + IsInitialized>(
account_info: &AccountInfo,
) -> Result<T, ProgramError> {
let account: T = T::unpack_unchecked(&account_info.data.borrow())?;
if account.is_initialized() {
Err(LendingError::AlreadyInitialized.into())
} else {
Ok(account)
}
}
/// Unpacks a spl_token `Mint`.
fn unpack_mint(data: &[u8]) -> Result<spl_token::state::Mint, LendingError> {
spl_token::state::Mint::unpack(data).map_err(|_| LendingError::InvalidTokenMint)
}
/// Issue a spl_token `InitializeMint` instruction.
#[inline(always)]
fn spl_token_init_mint(params: TokenInitializeMintParams<'_, '_>) -> ProgramResult {
let TokenInitializeMintParams {
mint,
rent,
authority,
token_program,
decimals,
} = params;
let ix = spl_token::instruction::initialize_mint(
token_program.key,
mint.key,
authority,
None,
decimals,
)?;
let result = invoke(&ix, &[mint, rent, token_program]);
result.map_err(|_| LendingError::TokenInitializeMintFailed.into())
}
/// Issue a spl_token `InitializeAccount` instruction.
#[inline(always)]
fn spl_token_init_account(params: TokenInitializeAccountParams<'_>) -> ProgramResult {
let TokenInitializeAccountParams {
account,
mint,
owner,
rent,
token_program,
} = params;
let ix = spl_token::instruction::initialize_account(
token_program.key,
account.key,
mint.key,
owner.key,
)?;
let result = invoke(&ix, &[account, mint, owner, rent, token_program]);
result.map_err(|_| LendingError::TokenInitializeAccountFailed.into())
}
/// Issue a spl_token `Transfer` instruction.
#[inline(always)]
fn spl_token_transfer(params: TokenTransferParams<'_, '_>) -> ProgramResult {
let TokenTransferParams {
source,
destination,
authority,
token_program,
amount,
authority_signer_seeds,
} = params;
let result = invoke_signed(
&spl_token::instruction::transfer(
token_program.key,
source.key,
destination.key,
authority.key,
&[],
amount,
)?,
&[source, destination, authority, token_program],
&[authority_signer_seeds],
);
result.map_err(|_| LendingError::TokenTransferFailed.into())
}
/// Issue a spl_token `MintTo` instruction.
fn spl_token_mint_to(params: TokenMintToParams<'_, '_>) -> ProgramResult {
let TokenMintToParams {
mint,
destination,
authority,
token_program,
amount,
authority_signer_seeds,
} = params;
let result = invoke_signed(
&spl_token::instruction::mint_to(
token_program.key,
mint.key,
destination.key,
authority.key,
&[],
amount,
)?,
&[mint, destination, authority, token_program],
&[authority_signer_seeds],
);
result.map_err(|_| LendingError::TokenMintToFailed.into())
}
/// Issue a spl_token `Burn` instruction.
#[inline(always)]
fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
let TokenBurnParams {
mint,
source,
authority,
token_program,
amount,
authority_signer_seeds,
} = params;
let result = invoke_signed(
&spl_token::instruction::burn(
token_program.key,
source.key,
mint.key,
authority.key,
&[],
amount,
)?,
&[source, mint, authority, token_program],
&[authority_signer_seeds],
);
result.map_err(|_| LendingError::TokenBurnFailed.into())
}
struct TokenInitializeMintParams<'a: 'b, 'b> {
mint: AccountInfo<'a>,
rent: AccountInfo<'a>,
authority: &'b Pubkey,
decimals: u8,
token_program: AccountInfo<'a>,
}
struct TokenInitializeAccountParams<'a> {
account: AccountInfo<'a>,
mint: AccountInfo<'a>,
owner: AccountInfo<'a>,
rent: AccountInfo<'a>,
token_program: AccountInfo<'a>,
}
struct TokenTransferParams<'a: 'b, 'b> {
source: AccountInfo<'a>,
destination: AccountInfo<'a>,
amount: u64,
authority: AccountInfo<'a>,
authority_signer_seeds: &'b [&'b [u8]],
token_program: AccountInfo<'a>,
}
struct TokenMintToParams<'a: 'b, 'b> {
mint: AccountInfo<'a>,
destination: AccountInfo<'a>,
amount: u64,
authority: AccountInfo<'a>,
authority_signer_seeds: &'b [&'b [u8]],
token_program: AccountInfo<'a>,
}
struct TokenBurnParams<'a: 'b, 'b> {
mint: AccountInfo<'a>,
source: AccountInfo<'a>,
amount: u64,
authority: AccountInfo<'a>,
authority_signer_seeds: &'b [&'b [u8]],
token_program: AccountInfo<'a>,
}
impl PrintProgramError for LendingError {
fn print<E>(&self)
where
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
{
msg!(&self.to_string());
}
}