Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 3045d7b

Browse files
leofisGjstarryjordaaash
authored
Flashloan for token lending (#1444)
Co-authored-by: Justin Starry <[email protected]> Co-authored-by: Jordan Sexton <[email protected]>
1 parent ea4b7e6 commit 3045d7b

File tree

11 files changed

+1115
-388
lines changed

11 files changed

+1115
-388
lines changed

Cargo.lock

Lines changed: 353 additions & 376 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

token-lending/flash_loan_design.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Flash Loan Design
2+
3+
We added a new instruction with the following signature for flash loan:
4+
```rust
5+
pub enum LendingInstruction {
6+
// ....
7+
/// Make a flash loan.
8+
///
9+
/// Accounts expected by this instruction:
10+
///
11+
/// 0. `[writable]` Source liquidity token account.
12+
/// Minted by reserve liquidity mint.
13+
/// Must match the reserve liquidity supply.
14+
/// 1. `[writable]` Destination liquidity token account.
15+
/// Minted by reserve liquidity mint.
16+
/// 2. `[writable]` Reserve account.
17+
/// 3. `[]` Lending market account.
18+
/// 4. `[]` Derived lending market authority.
19+
/// 5. `[]` Flash loan receiver program account.
20+
/// Must implement an instruction that has tag of 0 and a signature of `(repay_amount: u64)`
21+
/// This instruction must return the amount to the source liquidity account.
22+
/// 6. `[]` Token program id.
23+
/// 7. `[writable]` Flash loan fee receiver account.
24+
/// Must match the reserve liquidity fee receiver.
25+
/// 8. `[writable]` Host fee receiver.
26+
/// .. `[any]` Additional accounts expected by the receiving program's `ReceiveFlashLoan` instruction.
27+
FlashLoan {
28+
/// The amount that is to be borrowed
29+
amount: u64,
30+
},
31+
}
32+
```
33+
34+
In the implementation, we do the following in order:
35+
36+
1. Perform safety checks and calculate fees
37+
2. Transfer `amount` from the source liquidity account to the destination liquidity account
38+
2. Call the `ReceiveFlashLoan` function (the flash loan receiver program is required to have this function with tag `0`).
39+
The additional account required for `ReceiveFlashLoan` is given from the 10th account of the `FlashLoan` instruction, i.e. after host fee receiver.
40+
3. Check that the returned amount with the fee is in the reserve account after the completion of `ReceiveFlashLoan` function.
41+
42+
The flash loan receiver program should have a `ReceiveFlashLoan` instruction which executes the user-defined operation and return the funds to the reserve in the end.
43+
44+
```rust
45+
pub enum FlashLoanReceiverInstruction {
46+
47+
/// Receive a flash loan and perform user-defined operation and finally return the fund back.
48+
///
49+
/// Accounts expected:
50+
///
51+
/// 0. `[writable]` Source liquidity (matching the destination from above).
52+
/// 1. `[writable]` Destination liquidity (matching the source from above).
53+
/// 2. `[]` Token program id
54+
/// .. `[any]` Additional accounts provided to the lending program's `FlashLoan` instruction above.
55+
ReceiveFlashLoan {
56+
// Amount that is loaned to the receiver program
57+
amount: u64
58+
}
59+
}
60+
61+
```
62+
63+
You can view a sample implementation [here](https://github.com/solana-labs/solana-program-library/tree/master/token-lending/program/tests/helpers/flash_loan_receiver.rs).

token-lending/program/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ pub enum LendingError {
153153
/// Oracle config is invalid
154154
#[error("Input oracle config is invalid")]
155155
InvalidOracleConfig,
156+
/// Not enough liquidity after flash loan
157+
#[error("Not enough liquidity after flash loan")]
158+
NotEnoughLiquidityAfterFlashLoan,
156159
}
157160

158161
impl From<LendingError> for ProgramError {

token-lending/program/src/instruction.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,47 @@ pub enum LendingInstruction {
269269
/// Amount of liquidity to repay - u64::MAX for up to 100% of borrowed amount
270270
liquidity_amount: u64,
271271
},
272+
273+
// 13
274+
/// Make a flash loan.
275+
///
276+
/// Accounts expected by this instruction:
277+
///
278+
/// 0. `[writable]` Source liquidity token account.
279+
/// Minted by reserve liquidity mint.
280+
/// Must match the reserve liquidity supply.
281+
/// 1. `[writable]` Destination liquidity token account.
282+
/// Minted by reserve liquidity mint.
283+
/// 2. `[writable]` Reserve account.
284+
/// 3. `[]` Lending market account.
285+
/// 4. `[]` Derived lending market authority.
286+
/// 5. `[]` Flash loan receiver program account.
287+
/// Must implement an instruction that has tag of 0 and a signature of `(repay_amount: u64)`
288+
/// This instruction must return the amount to the source liquidity account.
289+
/// 6. `[]` Token program id.
290+
/// 7. `[writable]` Flash loan fee receiver account.
291+
/// Must match the reserve liquidity fee receiver.
292+
/// 8. `[writable]` Host fee receiver.
293+
/// .. `[any]` Additional accounts expected by the receiving program's `ReceiveFlashLoan` instruction.
294+
///
295+
/// The flash loan receiver program that is to be invoked should contain an instruction with
296+
/// tag `0` and accept the total amount (including fee) that needs to be returned back after
297+
/// its execution has completed.
298+
///
299+
/// Flash loan receiver should have an instruction with the following signature:
300+
///
301+
/// 0. `[writable]` Source liquidity (matching the destination from above).
302+
/// 1. `[writable]` Destination liquidity (matching the source from above).
303+
/// 2. `[]` Token program id
304+
/// .. `[any]` Additional accounts provided to the lending program's `FlashLoan` instruction above.
305+
/// ReceiveFlashLoan {
306+
/// // Amount that is loaned to the receiver program
307+
/// amount: u64
308+
/// }
309+
FlashLoan {
310+
/// The amount that is to be borrowed - u64::MAX for up to 100% of available liquidity
311+
amount: u64,
312+
},
272313
}
273314

274315
impl LendingInstruction {
@@ -296,6 +337,7 @@ impl LendingInstruction {
296337
let (optimal_borrow_rate, rest) = Self::unpack_u8(rest)?;
297338
let (max_borrow_rate, rest) = Self::unpack_u8(rest)?;
298339
let (borrow_fee_wad, rest) = Self::unpack_u64(rest)?;
340+
let (flash_loan_fee_wad, rest) = Self::unpack_u64(rest)?;
299341
let (host_fee_percentage, _rest) = Self::unpack_u8(rest)?;
300342
Self::InitReserve {
301343
liquidity_amount,
@@ -309,6 +351,7 @@ impl LendingInstruction {
309351
max_borrow_rate,
310352
fees: ReserveFees {
311353
borrow_fee_wad,
354+
flash_loan_fee_wad,
312355
host_fee_percentage,
313356
},
314357
},
@@ -345,6 +388,10 @@ impl LendingInstruction {
345388
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
346389
Self::LiquidateObligation { liquidity_amount }
347390
}
391+
13 => {
392+
let (amount, _rest) = Self::unpack_u64(rest)?;
393+
Self::FlashLoan { amount }
394+
}
348395
_ => {
349396
msg!("Instruction cannot be unpacked");
350397
return Err(LendingError::InstructionUnpackError.into());
@@ -416,6 +463,7 @@ impl LendingInstruction {
416463
fees:
417464
ReserveFees {
418465
borrow_fee_wad,
466+
flash_loan_fee_wad,
419467
host_fee_percentage,
420468
},
421469
},
@@ -430,6 +478,7 @@ impl LendingInstruction {
430478
buf.extend_from_slice(&optimal_borrow_rate.to_le_bytes());
431479
buf.extend_from_slice(&max_borrow_rate.to_le_bytes());
432480
buf.extend_from_slice(&borrow_fee_wad.to_le_bytes());
481+
buf.extend_from_slice(&flash_loan_fee_wad.to_le_bytes());
433482
buf.extend_from_slice(&host_fee_percentage.to_le_bytes());
434483
}
435484
Self::RefreshReserve => {
@@ -469,6 +518,10 @@ impl LendingInstruction {
469518
buf.push(12);
470519
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
471520
}
521+
Self::FlashLoan { amount } => {
522+
buf.push(13);
523+
buf.extend_from_slice(&amount.to_le_bytes());
524+
}
472525
}
473526
buf
474527
}
@@ -884,3 +937,37 @@ pub fn liquidate_obligation(
884937
data: LendingInstruction::LiquidateObligation { liquidity_amount }.pack(),
885938
}
886939
}
940+
941+
/// Creates a `FlashLoan` instruction.
942+
#[allow(clippy::too_many_arguments)]
943+
pub fn flash_loan(
944+
program_id: Pubkey,
945+
amount: u64,
946+
reserve_liquidity_pubkey: Pubkey,
947+
destination_liquidity_pubkey: Pubkey,
948+
reserve_pubkey: Pubkey,
949+
lending_market_pubkey: Pubkey,
950+
derived_lending_market_authority_pubkey: Pubkey,
951+
flash_loan_receiver_pubkey: Pubkey,
952+
flash_loan_fee_receiver_pubkey: Pubkey,
953+
host_fee_receiver_pubkey: Pubkey,
954+
flash_loan_receiver_program_account_meta: Vec<AccountMeta>,
955+
) -> Instruction {
956+
let mut accounts = vec![
957+
AccountMeta::new(reserve_liquidity_pubkey, false),
958+
AccountMeta::new(destination_liquidity_pubkey, false),
959+
AccountMeta::new_readonly(reserve_pubkey, false),
960+
AccountMeta::new_readonly(lending_market_pubkey, false),
961+
AccountMeta::new_readonly(derived_lending_market_authority_pubkey, false),
962+
AccountMeta::new_readonly(flash_loan_receiver_pubkey, false),
963+
AccountMeta::new(flash_loan_fee_receiver_pubkey, false),
964+
AccountMeta::new(host_fee_receiver_pubkey, false),
965+
AccountMeta::new_readonly(spl_token::id(), false),
966+
];
967+
accounts.extend(flash_loan_receiver_program_account_meta);
968+
Instruction {
969+
program_id,
970+
accounts,
971+
data: LendingInstruction::FlashLoan { amount }.pack(),
972+
}
973+
}

0 commit comments

Comments
 (0)