Skip to content

Commit 9ffd16d

Browse files
donate to reserve function (#194)
* donate to reserve function * fixes * fixes2 * fix CI * check state * fmt * pr feedback * fix CI --------- Co-authored-by: 0xripleys <[email protected]>
1 parent dc26dd7 commit 9ffd16d

File tree

7 files changed

+240
-3
lines changed

7 files changed

+240
-3
lines changed

.github/workflows/pull-request-token-lending.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
run: ./ci/cargo-test-bpf.sh token-lending
6060

6161
- name: Upload programs
62-
uses: actions/upload-artifact@v2
62+
uses: actions/upload-artifact@v4
6363
with:
6464
name: token-lending-programs
6565
path: "target/deploy/*.so"

ci/cargo-test-bpf.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ run_dir=$(pwd)
3333
if [[ -d $run_dir/program ]]; then
3434
# Build/test just one BPF program
3535
cd $run_dir/program
36-
RUST_LOG="error" cargo +"$rust_stable" test-bpf -j 1 -- --nocapture
36+
RUST_LOG="error" cargo +"$rust_stable" test-bpf --features test-bpf -j 1 -- --nocapture
3737
else
3838
# Build/test all BPF programs
3939
for directory in $(ls -d $run_dir/*/); do
4040
cd $directory
41-
RUST_LOG="error" cargo +"$rust_stable" test-bpf -j 1 -- --nocapture
41+
RUST_LOG="error" cargo +"$rust_stable" test-bpf --features test-bpf -j 1 -- --nocapture
4242
done
4343
fi

token-lending/program/src/processor.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use oracles::pyth::validate_pyth_keys;
2020
use oracles::switchboard::validate_sb_on_demand_keys;
2121
use oracles::switchboard::validate_switchboard_keys;
2222
use oracles::{get_oracle_type, pyth::validate_pyth_price_account_info, OracleType};
23+
#[cfg(not(feature = "test-bpf"))]
24+
use solana_program::pubkey;
2325
use solana_program::{
2426
account_info::{next_account_info, AccountInfo},
2527
entrypoint::ProgramResult,
@@ -196,6 +198,10 @@ pub fn process_instruction(
196198
msg!("Instruction: Mark Obligation As Closable");
197199
process_set_obligation_closeability_status(program_id, closeable, accounts)
198200
}
201+
LendingInstruction::DonateToReserve { liquidity_amount } => {
202+
msg!("Instruction: Donate To Reserve");
203+
process_donate_to_reserve(program_id, liquidity_amount, accounts)
204+
}
199205
}
200206
}
201207

@@ -3187,6 +3193,76 @@ pub fn process_set_obligation_closeability_status(
31873193
Ok(())
31883194
}
31893195

3196+
/// process donate to reserve
3197+
pub fn process_donate_to_reserve(
3198+
program_id: &Pubkey,
3199+
liquidity_amount: u64,
3200+
accounts: &[AccountInfo],
3201+
) -> ProgramResult {
3202+
let account_info_iter = &mut accounts.iter();
3203+
let source_liquidity_info = next_account_info(account_info_iter)?;
3204+
let destination_liquidity_info = next_account_info(account_info_iter)?;
3205+
let reserve_info = next_account_info(account_info_iter)?;
3206+
let lending_market_info = next_account_info(account_info_iter)?;
3207+
let user_transfer_authority_info = next_account_info(account_info_iter)?;
3208+
let token_program_id = next_account_info(account_info_iter)?;
3209+
let clock = &Clock::get()?;
3210+
3211+
let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
3212+
if lending_market_info.owner != program_id {
3213+
msg!("Lending market provided is not owned by the lending program");
3214+
return Err(LendingError::InvalidAccountOwner.into());
3215+
}
3216+
if &lending_market.token_program_id != token_program_id.key {
3217+
msg!("Lending market token program does not match the token program provided");
3218+
return Err(LendingError::InvalidTokenProgram.into());
3219+
}
3220+
3221+
if reserve_info.owner != program_id {
3222+
msg!("Lending market provided is not owned by the lending program");
3223+
return Err(LendingError::InvalidAccountOwner.into());
3224+
}
3225+
3226+
let mut reserve = Box::new(Reserve::unpack(&reserve_info.data.borrow())?);
3227+
if &reserve.lending_market != lending_market_info.key {
3228+
msg!("Reserve lending market does not match the lending market provided");
3229+
return Err(LendingError::InvalidAccountInput.into());
3230+
}
3231+
3232+
if &reserve.liquidity.supply_pubkey != destination_liquidity_info.key {
3233+
msg!("Reserve liquidity supply does not match the reserve liquidity supply provided");
3234+
return Err(LendingError::InvalidAccountInput.into());
3235+
}
3236+
3237+
if &reserve.liquidity.supply_pubkey == source_liquidity_info.key {
3238+
msg!("Reserve liquidity supply cannot be used as the source liquidity provided");
3239+
return Err(LendingError::InvalidAccountInput.into());
3240+
}
3241+
3242+
#[cfg(not(feature = "test-bpf"))]
3243+
if *reserve_info.key != pubkey!("6LRNkS4Aq6VZ9Np36o7RDZ9aztWCePekMgiFgUNDhXXN") {
3244+
msg!("Donate function is currently limited to JUP pool usdc");
3245+
return Err(LendingError::InvalidAccountInput.into());
3246+
}
3247+
3248+
_refresh_reserve_interest(program_id, reserve_info, clock)?;
3249+
3250+
reserve.liquidity.donate(liquidity_amount)?;
3251+
spl_token_transfer(TokenTransferParams {
3252+
source: source_liquidity_info.clone(),
3253+
destination: destination_liquidity_info.clone(),
3254+
amount: liquidity_amount,
3255+
authority: user_transfer_authority_info.clone(),
3256+
authority_signer_seeds: &[],
3257+
token_program: token_program_id.clone(),
3258+
})?;
3259+
3260+
reserve.last_update.mark_stale();
3261+
Reserve::pack(*reserve, &mut reserve_info.data.borrow_mut())?;
3262+
3263+
Ok(())
3264+
}
3265+
31903266
fn assert_uninitialized<T: Pack + IsInitialized>(
31913267
account_info: &AccountInfo,
31923268
) -> Result<T, ProgramError> {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#![cfg(feature = "test-bpf")]
2+
use crate::solend_program_test::custom_scenario;
3+
4+
use crate::solend_program_test::User;
5+
6+
use crate::solend_program_test::BalanceChecker;
7+
8+
use crate::solend_program_test::PriceArgs;
9+
use crate::solend_program_test::ReserveArgs;
10+
use crate::solend_program_test::TokenBalanceChange;
11+
12+
mod helpers;
13+
14+
use helpers::*;
15+
use solana_program_test::*;
16+
use solend_sdk::state::Reserve;
17+
18+
use std::collections::HashSet;
19+
20+
#[tokio::test]
21+
async fn test_donate_to_reserve() {
22+
let (mut test, lending_market, reserves, _obligations, _users, _) = custom_scenario(
23+
&[ReserveArgs {
24+
mint: usdc_mint::id(),
25+
config: test_reserve_config(),
26+
liquidity_amount: 100_000 * FRACTIONAL_TO_USDC,
27+
price: PriceArgs {
28+
price: 10,
29+
conf: 0,
30+
expo: -1,
31+
ema_price: 10,
32+
ema_conf: 1,
33+
},
34+
}],
35+
&[],
36+
)
37+
.await;
38+
39+
let whale = User::new_with_balances(
40+
&mut test,
41+
&[(&usdc_mint::id(), 100_000 * FRACTIONAL_TO_USDC)],
42+
)
43+
.await;
44+
45+
let balance_checker = BalanceChecker::start(&mut test, &[&whale, &reserves[0]]).await;
46+
47+
lending_market
48+
.donate_to_reserve(
49+
&mut test,
50+
&reserves[0],
51+
&whale,
52+
100_000 * FRACTIONAL_TO_USDC,
53+
)
54+
.await
55+
.unwrap();
56+
57+
let reserve_post = test.load_account::<Reserve>(reserves[0].pubkey).await;
58+
59+
assert_eq!(
60+
reserve_post.account.liquidity.available_amount,
61+
200_000 * FRACTIONAL_TO_USDC
62+
);
63+
64+
let (balance_changes, _) = balance_checker.find_balance_changes(&mut test).await;
65+
let expected_balance_changes = HashSet::from([
66+
TokenBalanceChange {
67+
token_account: whale.get_account(&usdc_mint::id()).unwrap(),
68+
mint: usdc_mint::id(),
69+
diff: -(100_000 * FRACTIONAL_TO_USDC as i128),
70+
},
71+
TokenBalanceChange {
72+
token_account: reserves[0].account.liquidity.supply_pubkey,
73+
mint: usdc_mint::id(),
74+
diff: 100_000 * FRACTIONAL_TO_USDC as i128,
75+
},
76+
]);
77+
78+
assert_eq!(balance_changes, expected_balance_changes);
79+
}

token-lending/program/tests/helpers/solend_program_test.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,31 @@ impl Info<LendingMarket> {
862862
.await
863863
}
864864

865+
pub async fn donate_to_reserve(
866+
&self,
867+
test: &mut SolendProgramTest,
868+
reserve: &Info<Reserve>,
869+
user: &User,
870+
liquidity_amount: u64,
871+
) -> Result<(), BanksClientError> {
872+
let instructions = [
873+
ComputeBudgetInstruction::set_compute_unit_limit(50_000),
874+
donate_to_reserve(
875+
solend_program::id(),
876+
liquidity_amount,
877+
user.get_account(&reserve.account.liquidity.mint_pubkey)
878+
.unwrap(),
879+
reserve.account.liquidity.supply_pubkey,
880+
reserve.pubkey,
881+
self.pubkey,
882+
user.keypair.pubkey(),
883+
),
884+
];
885+
886+
test.process_transaction(&instructions, Some(&[&user.keypair]))
887+
.await
888+
}
889+
865890
pub async fn update_reserve_config(
866891
&self,
867892
test: &mut SolendProgramTest,

token-lending/sdk/src/instruction.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,22 @@ pub enum LendingInstruction {
512512
/// Obligation is closable
513513
closeable: bool,
514514
},
515+
516+
// 24
517+
/// DonateToReserve
518+
///
519+
/// 0. `[writable]` Source liquidity token account.
520+
/// Minted by repay reserve liquidity mint.
521+
/// $authority can transfer $liquidity_amount.
522+
/// 1. `[writable]` Destination reserve liquidity supply SPL Token account.
523+
/// 2. `[writable]` Repay reserve account - refreshed.
524+
/// 3. `[]` Lending market account.
525+
/// 4. `[signer]` User transfer authority ($authority).
526+
/// 5. `[]` Token program id.
527+
DonateToReserve {
528+
/// amount to donate
529+
liquidity_amount: u64,
530+
},
515531
}
516532

517533
impl LendingInstruction {
@@ -766,6 +782,10 @@ impl LendingInstruction {
766782

767783
Self::SetObligationCloseabilityStatus { closeable }
768784
}
785+
24 => {
786+
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
787+
Self::DonateToReserve { liquidity_amount }
788+
}
769789
_ => {
770790
msg!("Instruction cannot be unpacked");
771791
return Err(LendingError::InstructionUnpackError.into());
@@ -1061,6 +1081,10 @@ impl LendingInstruction {
10611081
buf.push(23);
10621082
buf.extend_from_slice(&(closeable as u8).to_le_bytes());
10631083
}
1084+
Self::DonateToReserve { liquidity_amount } => {
1085+
buf.push(24);
1086+
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
1087+
}
10641088
}
10651089
buf
10661090
}
@@ -1849,6 +1873,30 @@ pub fn set_obligation_closeability_status(
18491873
}
18501874
}
18511875

1876+
/// Creates a `DonateToReserve` instruction
1877+
pub fn donate_to_reserve(
1878+
program_id: Pubkey,
1879+
liquidity_amount: u64,
1880+
source_liquidity_pubkey: Pubkey,
1881+
destination_liquidity_pubkey: Pubkey,
1882+
reserve_pubkey: Pubkey,
1883+
lending_market_pubkey: Pubkey,
1884+
user_transfer_authority_pubkey: Pubkey,
1885+
) -> Instruction {
1886+
Instruction {
1887+
program_id,
1888+
accounts: vec![
1889+
AccountMeta::new(source_liquidity_pubkey, false),
1890+
AccountMeta::new(destination_liquidity_pubkey, false),
1891+
AccountMeta::new(reserve_pubkey, false),
1892+
AccountMeta::new_readonly(lending_market_pubkey, false),
1893+
AccountMeta::new_readonly(user_transfer_authority_pubkey, true),
1894+
AccountMeta::new_readonly(spl_token::id(), false),
1895+
],
1896+
data: LendingInstruction::DonateToReserve { liquidity_amount }.pack(),
1897+
}
1898+
}
1899+
18521900
#[cfg(test)]
18531901
mod test {
18541902
use super::*;

token-lending/sdk/src/state/reserve.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,15 @@ impl ReserveLiquidity {
751751
Ok(())
752752
}
753753

754+
/// Add donate_amount to available liquidity and subtract settle amount from total borrows
755+
pub fn donate(&mut self, donate_amount: u64) -> ProgramResult {
756+
self.available_amount = self
757+
.available_amount
758+
.checked_add(donate_amount)
759+
.ok_or(LendingError::MathOverflow)?;
760+
Ok(())
761+
}
762+
754763
/// Subtract settle amount from accumulated_protocol_fees_wads and withdraw_amount from available liquidity
755764
pub fn redeem_fees(&mut self, withdraw_amount: u64) -> ProgramResult {
756765
self.available_amount = self

0 commit comments

Comments
 (0)