Skip to content

Commit f960664

Browse files
authored
added liquidate and redeem (with protocol fee) (#75)
* added liquidated and redeem (with protocol fee) * pr comments * working tests * protocol liquidation fee configurable * adding protocol liquidation fee to cli * justin feedback * setting fee to 0 for initial deploy
1 parent 73260f0 commit f960664

File tree

7 files changed

+494
-22
lines changed

7 files changed

+494
-22
lines changed

token-lending/cli/src/main.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ struct PartialReserveConfig {
6666
pub borrow_limit: Option<u64>,
6767
/// Liquidity fee receiver
6868
pub fee_receiver: Option<Pubkey>,
69+
/// Cut of the liquidation bonus that the protocol receives, as a percentage
70+
pub protocol_liquidation_fee: Option<u8>,
6971
}
7072

7173
/// Reserve Fees with optional fields
@@ -646,6 +648,8 @@ fn main() {
646648
let flash_loan_fee_wad = (flash_loan_fee * WAD as f64) as u64;
647649

648650
let liquidity_fee_receiver_keypair = Keypair::new();
651+
let protocol_liquidation_fee =
652+
value_of(arg_matches, "protocol_liquidation_fee").unwrap();
649653

650654
let source_liquidity_account = config
651655
.rpc_client
@@ -683,6 +687,7 @@ fn main() {
683687
deposit_limit,
684688
borrow_limit,
685689
fee_receiver: liquidity_fee_receiver_keypair.pubkey(),
690+
protocol_liquidation_fee,
686691
},
687692
source_liquidity_pubkey,
688693
source_liquidity_owner_keypair,
@@ -713,6 +718,7 @@ fn main() {
713718
let deposit_limit = value_of(arg_matches, "deposit_limit");
714719
let borrow_limit = value_of(arg_matches, "borrow_limit");
715720
let fee_receiver = pubkey_of(arg_matches, "fee_receiver");
721+
let protocol_liquidation_fee = value_of(arg_matches, "protocol_liquidation_fee");
716722
let pyth_product_pubkey = pubkey_of(arg_matches, "pyth_product");
717723
let pyth_price_pubkey = pubkey_of(arg_matches, "pyth_price");
718724
let switchboard_feed_pubkey = pubkey_of(arg_matches, "switchboard_feed");
@@ -738,6 +744,7 @@ fn main() {
738744
deposit_limit,
739745
borrow_limit,
740746
fee_receiver,
747+
protocol_liquidation_fee,
741748
},
742749
pyth_product_pubkey,
743750
pyth_price_pubkey,
@@ -1164,6 +1171,15 @@ fn command_update_reserve(
11641171
reserve.config.fee_receiver = reserve_config.fee_receiver.unwrap();
11651172
}
11661173

1174+
if reserve_config.protocol_liquidation_fee.is_some() {
1175+
println!(
1176+
"Updating protocol_liquidation_fee from {} to {}",
1177+
reserve.config.protocol_liquidation_fee,
1178+
reserve_config.protocol_liquidation_fee.unwrap(),
1179+
);
1180+
reserve.config.protocol_liquidation_fee = reserve_config.protocol_liquidation_fee.unwrap();
1181+
}
1182+
11671183
let mut new_pyth_product_pubkey = spl_token_lending::NULL_PUBKEY;
11681184
if pyth_price_pubkey.is_some() {
11691185
println!(

token-lending/program/src/instruction.rs

Lines changed: 107 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,35 @@ pub enum LendingInstruction {
379379
/// Reserve config to update to
380380
config: ReserveConfig,
381381
},
382+
383+
// 17
384+
/// Repay borrowed liquidity to a reserve to receive collateral at a discount from an unhealthy
385+
/// obligation. Requires a refreshed obligation and reserves.
386+
///
387+
/// Accounts expected by this instruction:
388+
///
389+
/// 0. `[writable]` Source liquidity token account.
390+
/// Minted by repay reserve liquidity mint.
391+
/// $authority can transfer $liquidity_amount.
392+
/// 1. `[writable]` Destination collateral token account.
393+
/// Minted by withdraw reserve collateral mint.
394+
/// 2. `[writable]` Destination liquidity token account.
395+
/// 3. `[writable]` Repay reserve account - refreshed.
396+
/// 4. `[writable]` Repay reserve liquidity supply SPL Token account.
397+
/// 5. `[writable]` Withdraw reserve account - refreshed.
398+
/// 6. `[writable]` Withdraw reserve collateral SPL Token mint.
399+
/// 7. `[writable]` Withdraw reserve collateral supply SPL Token account.
400+
/// 8. `[writable]` Withdraw reserve liquidity supply SPL Token account.
401+
/// 9. `[writable]` Withdraw reserve liquidity fee receiver account.
402+
/// 10 `[writable]` Obligation account - refreshed.
403+
/// 11 `[]` Lending market account.
404+
/// 12 `[]` Derived lending market authority.
405+
/// 13 `[signer]` User transfer authority ($authority).
406+
/// 14 `[]` Token program id.
407+
LiquidateObligationAndRedeemReserveCollateral {
408+
/// Amount of liquidity to repay - u64::MAX for up to 100% of borrowed amount
409+
liquidity_amount: u64,
410+
},
382411
}
383412

384413
impl LendingInstruction {
@@ -414,7 +443,8 @@ impl LendingInstruction {
414443
let (host_fee_percentage, rest) = Self::unpack_u8(rest)?;
415444
let (deposit_limit, rest) = Self::unpack_u64(rest)?;
416445
let (borrow_limit, rest) = Self::unpack_u64(rest)?;
417-
let (fee_receiver, _) = Self::unpack_pubkey(rest)?;
446+
let (fee_receiver, rest) = Self::unpack_pubkey(rest)?;
447+
let (protocol_liquidation_fee, _rest) = Self::unpack_u8(rest)?;
418448
Self::InitReserve {
419449
liquidity_amount,
420450
config: ReserveConfig {
@@ -433,6 +463,7 @@ impl LendingInstruction {
433463
deposit_limit,
434464
borrow_limit,
435465
fee_receiver,
466+
protocol_liquidation_fee,
436467
},
437468
}
438469
}
@@ -480,20 +511,20 @@ impl LendingInstruction {
480511
Self::WithdrawObligationCollateralAndRedeemReserveCollateral { collateral_amount }
481512
}
482513
16 => {
483-
let (optimal_utilization_rate, _rest) = Self::unpack_u8(rest)?;
484-
let (loan_to_value_ratio, _rest) = Self::unpack_u8(_rest)?;
485-
let (liquidation_bonus, _rest) = Self::unpack_u8(_rest)?;
486-
let (liquidation_threshold, _rest) = Self::unpack_u8(_rest)?;
487-
let (min_borrow_rate, _rest) = Self::unpack_u8(_rest)?;
488-
let (optimal_borrow_rate, _rest) = Self::unpack_u8(_rest)?;
489-
let (max_borrow_rate, _rest) = Self::unpack_u8(_rest)?;
490-
let (borrow_fee_wad, _rest) = Self::unpack_u64(_rest)?;
491-
let (flash_loan_fee_wad, _rest) = Self::unpack_u64(_rest)?;
492-
let (host_fee_percentage, _rest) = Self::unpack_u8(_rest)?;
493-
let (deposit_limit, _rest) = Self::unpack_u64(_rest)?;
494-
let (borrow_limit, _rest) = Self::unpack_u64(_rest)?;
495-
let (fee_receiver, _) = Self::unpack_pubkey(_rest)?;
496-
514+
let (optimal_utilization_rate, rest) = Self::unpack_u8(rest)?;
515+
let (loan_to_value_ratio, rest) = Self::unpack_u8(rest)?;
516+
let (liquidation_bonus, rest) = Self::unpack_u8(rest)?;
517+
let (liquidation_threshold, rest) = Self::unpack_u8(rest)?;
518+
let (min_borrow_rate, rest) = Self::unpack_u8(rest)?;
519+
let (optimal_borrow_rate, rest) = Self::unpack_u8(rest)?;
520+
let (max_borrow_rate, rest) = Self::unpack_u8(rest)?;
521+
let (borrow_fee_wad, rest) = Self::unpack_u64(rest)?;
522+
let (flash_loan_fee_wad, rest) = Self::unpack_u64(rest)?;
523+
let (host_fee_percentage, rest) = Self::unpack_u8(rest)?;
524+
let (deposit_limit, rest) = Self::unpack_u64(rest)?;
525+
let (borrow_limit, rest) = Self::unpack_u64(rest)?;
526+
let (fee_receiver, rest) = Self::unpack_pubkey(rest)?;
527+
let (protocol_liquidation_fee, _rest) = Self::unpack_u8(rest)?;
497528
Self::UpdateReserveConfig {
498529
config: ReserveConfig {
499530
optimal_utilization_rate,
@@ -511,9 +542,14 @@ impl LendingInstruction {
511542
deposit_limit,
512543
borrow_limit,
513544
fee_receiver,
545+
protocol_liquidation_fee,
514546
},
515547
}
516548
}
549+
17 => {
550+
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
551+
Self::LiquidateObligationAndRedeemReserveCollateral { liquidity_amount }
552+
}
517553
_ => {
518554
msg!("Instruction cannot be unpacked");
519555
return Err(LendingError::InstructionUnpackError.into());
@@ -609,6 +645,7 @@ impl LendingInstruction {
609645
deposit_limit,
610646
borrow_limit,
611647
fee_receiver,
648+
protocol_liquidation_fee,
612649
},
613650
} => {
614651
buf.push(2);
@@ -626,6 +663,7 @@ impl LendingInstruction {
626663
buf.extend_from_slice(&deposit_limit.to_le_bytes());
627664
buf.extend_from_slice(&borrow_limit.to_le_bytes());
628665
buf.extend_from_slice(&fee_receiver.to_bytes());
666+
buf.extend_from_slice(&protocol_liquidation_fee.to_le_bytes());
629667
}
630668
Self::RefreshReserve => {
631669
buf.push(3);
@@ -691,6 +729,11 @@ impl LendingInstruction {
691729
buf.extend_from_slice(&config.deposit_limit.to_le_bytes());
692730
buf.extend_from_slice(&config.borrow_limit.to_le_bytes());
693731
buf.extend_from_slice(&config.fee_receiver.to_bytes());
732+
buf.extend_from_slice(&config.protocol_liquidation_fee.to_le_bytes());
733+
}
734+
Self::LiquidateObligationAndRedeemReserveCollateral { liquidity_amount } => {
735+
buf.push(17);
736+
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
694737
}
695738
}
696739
buf
@@ -1216,3 +1259,52 @@ pub fn update_reserve_config(
12161259
data: LendingInstruction::UpdateReserveConfig { config }.pack(),
12171260
}
12181261
}
1262+
1263+
/// Creates a `LiquidateObligationAndRedeemReserveCollateral` instruction
1264+
#[allow(clippy::too_many_arguments)]
1265+
pub fn liquidate_obligation_and_redeem_reserve_collateral(
1266+
program_id: Pubkey,
1267+
liquidity_amount: u64,
1268+
source_liquidity_pubkey: Pubkey,
1269+
destination_collateral_pubkey: Pubkey,
1270+
destination_liquidity_pubkey: Pubkey,
1271+
repay_reserve_pubkey: Pubkey,
1272+
repay_reserve_liquidity_supply_pubkey: Pubkey,
1273+
withdraw_reserve_pubkey: Pubkey,
1274+
withdraw_reserve_collateral_mint_pubkey: Pubkey,
1275+
withdraw_reserve_collateral_supply_pubkey: Pubkey,
1276+
withdraw_reserve_liquidity_supply_pubkey: Pubkey,
1277+
withdraw_reserve_liquidity_fee_receiver_pubkey: Pubkey,
1278+
obligation_pubkey: Pubkey,
1279+
lending_market_pubkey: Pubkey,
1280+
user_transfer_authority_pubkey: Pubkey,
1281+
) -> Instruction {
1282+
let (lending_market_authority_pubkey, _bump_seed) = Pubkey::find_program_address(
1283+
&[&lending_market_pubkey.to_bytes()[..PUBKEY_BYTES]],
1284+
&program_id,
1285+
);
1286+
Instruction {
1287+
program_id,
1288+
accounts: vec![
1289+
AccountMeta::new(source_liquidity_pubkey, false),
1290+
AccountMeta::new(destination_collateral_pubkey, false),
1291+
AccountMeta::new(destination_liquidity_pubkey, false),
1292+
AccountMeta::new(repay_reserve_pubkey, false),
1293+
AccountMeta::new(repay_reserve_liquidity_supply_pubkey, false),
1294+
AccountMeta::new(withdraw_reserve_pubkey, false),
1295+
AccountMeta::new(withdraw_reserve_collateral_mint_pubkey, false),
1296+
AccountMeta::new(withdraw_reserve_collateral_supply_pubkey, false),
1297+
AccountMeta::new(withdraw_reserve_liquidity_supply_pubkey, false),
1298+
AccountMeta::new(withdraw_reserve_liquidity_fee_receiver_pubkey, false),
1299+
AccountMeta::new(obligation_pubkey, false),
1300+
AccountMeta::new_readonly(lending_market_pubkey, false),
1301+
AccountMeta::new_readonly(lending_market_authority_pubkey, false),
1302+
AccountMeta::new_readonly(user_transfer_authority_pubkey, true),
1303+
AccountMeta::new_readonly(spl_token::id(), false),
1304+
],
1305+
data: LendingInstruction::LiquidateObligationAndRedeemReserveCollateral {
1306+
liquidity_amount,
1307+
}
1308+
.pack(),
1309+
}
1310+
}

0 commit comments

Comments
 (0)