Skip to content

Commit

Permalink
donate to reserve function (#194)
Browse files Browse the repository at this point in the history
* donate to reserve function

* fixes

* fixes2

* fix CI

* check state

* fmt

* pr feedback

* fix CI

---------

Co-authored-by: 0xripleys <0xripleys@solend.fi>
  • Loading branch information
nope-finance and 0xripleys authored Oct 18, 2024
1 parent dc26dd7 commit 9ffd16d
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pull-request-token-lending.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
run: ./ci/cargo-test-bpf.sh token-lending

- name: Upload programs
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: token-lending-programs
path: "target/deploy/*.so"
Expand Down
4 changes: 2 additions & 2 deletions ci/cargo-test-bpf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ run_dir=$(pwd)
if [[ -d $run_dir/program ]]; then
# Build/test just one BPF program
cd $run_dir/program
RUST_LOG="error" cargo +"$rust_stable" test-bpf -j 1 -- --nocapture
RUST_LOG="error" cargo +"$rust_stable" test-bpf --features test-bpf -j 1 -- --nocapture
else
# Build/test all BPF programs
for directory in $(ls -d $run_dir/*/); do
cd $directory
RUST_LOG="error" cargo +"$rust_stable" test-bpf -j 1 -- --nocapture
RUST_LOG="error" cargo +"$rust_stable" test-bpf --features test-bpf -j 1 -- --nocapture
done
fi
76 changes: 76 additions & 0 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use oracles::pyth::validate_pyth_keys;
use oracles::switchboard::validate_sb_on_demand_keys;
use oracles::switchboard::validate_switchboard_keys;
use oracles::{get_oracle_type, pyth::validate_pyth_price_account_info, OracleType};
#[cfg(not(feature = "test-bpf"))]
use solana_program::pubkey;
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
Expand Down Expand Up @@ -196,6 +198,10 @@ pub fn process_instruction(
msg!("Instruction: Mark Obligation As Closable");
process_set_obligation_closeability_status(program_id, closeable, accounts)
}
LendingInstruction::DonateToReserve { liquidity_amount } => {
msg!("Instruction: Donate To Reserve");
process_donate_to_reserve(program_id, liquidity_amount, accounts)
}
}
}

Expand Down Expand Up @@ -3187,6 +3193,76 @@ pub fn process_set_obligation_closeability_status(
Ok(())
}

/// process donate to reserve
pub fn process_donate_to_reserve(
program_id: &Pubkey,
liquidity_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let source_liquidity_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 lending_market_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let token_program_id = next_account_info(account_info_iter)?;
let clock = &Clock::get()?;

let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
if lending_market_info.owner != program_id {
msg!("Lending market provided is not owned by the lending program");
return Err(LendingError::InvalidAccountOwner.into());
}
if &lending_market.token_program_id != token_program_id.key {
msg!("Lending market token program does not match the token program provided");
return Err(LendingError::InvalidTokenProgram.into());
}

if reserve_info.owner != program_id {
msg!("Lending market provided is not owned by the lending program");
return Err(LendingError::InvalidAccountOwner.into());
}

let mut reserve = Box::new(Reserve::unpack(&reserve_info.data.borrow())?);
if &reserve.lending_market != lending_market_info.key {
msg!("Reserve lending market does not match the lending market provided");
return Err(LendingError::InvalidAccountInput.into());
}

if &reserve.liquidity.supply_pubkey != destination_liquidity_info.key {
msg!("Reserve liquidity supply does not match the reserve liquidity supply provided");
return Err(LendingError::InvalidAccountInput.into());
}

if &reserve.liquidity.supply_pubkey == source_liquidity_info.key {
msg!("Reserve liquidity supply cannot be used as the source liquidity provided");
return Err(LendingError::InvalidAccountInput.into());
}

#[cfg(not(feature = "test-bpf"))]
if *reserve_info.key != pubkey!("6LRNkS4Aq6VZ9Np36o7RDZ9aztWCePekMgiFgUNDhXXN") {
msg!("Donate function is currently limited to JUP pool usdc");
return Err(LendingError::InvalidAccountInput.into());
}

_refresh_reserve_interest(program_id, reserve_info, clock)?;

reserve.liquidity.donate(liquidity_amount)?;
spl_token_transfer(TokenTransferParams {
source: source_liquidity_info.clone(),
destination: destination_liquidity_info.clone(),
amount: liquidity_amount,
authority: user_transfer_authority_info.clone(),
authority_signer_seeds: &[],
token_program: token_program_id.clone(),
})?;

reserve.last_update.mark_stale();
Reserve::pack(*reserve, &mut reserve_info.data.borrow_mut())?;

Ok(())
}

fn assert_uninitialized<T: Pack + IsInitialized>(
account_info: &AccountInfo,
) -> Result<T, ProgramError> {
Expand Down
79 changes: 79 additions & 0 deletions token-lending/program/tests/donate_to_reserve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#![cfg(feature = "test-bpf")]
use crate::solend_program_test::custom_scenario;

use crate::solend_program_test::User;

use crate::solend_program_test::BalanceChecker;

use crate::solend_program_test::PriceArgs;
use crate::solend_program_test::ReserveArgs;
use crate::solend_program_test::TokenBalanceChange;

mod helpers;

use helpers::*;
use solana_program_test::*;
use solend_sdk::state::Reserve;

use std::collections::HashSet;

#[tokio::test]
async fn test_donate_to_reserve() {
let (mut test, lending_market, reserves, _obligations, _users, _) = custom_scenario(
&[ReserveArgs {
mint: usdc_mint::id(),
config: test_reserve_config(),
liquidity_amount: 100_000 * FRACTIONAL_TO_USDC,
price: PriceArgs {
price: 10,
conf: 0,
expo: -1,
ema_price: 10,
ema_conf: 1,
},
}],
&[],
)
.await;

let whale = User::new_with_balances(
&mut test,
&[(&usdc_mint::id(), 100_000 * FRACTIONAL_TO_USDC)],
)
.await;

let balance_checker = BalanceChecker::start(&mut test, &[&whale, &reserves[0]]).await;

lending_market
.donate_to_reserve(
&mut test,
&reserves[0],
&whale,
100_000 * FRACTIONAL_TO_USDC,
)
.await
.unwrap();

let reserve_post = test.load_account::<Reserve>(reserves[0].pubkey).await;

assert_eq!(
reserve_post.account.liquidity.available_amount,
200_000 * FRACTIONAL_TO_USDC
);

let (balance_changes, _) = balance_checker.find_balance_changes(&mut test).await;
let expected_balance_changes = HashSet::from([
TokenBalanceChange {
token_account: whale.get_account(&usdc_mint::id()).unwrap(),
mint: usdc_mint::id(),
diff: -(100_000 * FRACTIONAL_TO_USDC as i128),
},
TokenBalanceChange {
token_account: reserves[0].account.liquidity.supply_pubkey,
mint: usdc_mint::id(),
diff: 100_000 * FRACTIONAL_TO_USDC as i128,
},
]);

assert_eq!(balance_changes, expected_balance_changes);
}
25 changes: 25 additions & 0 deletions token-lending/program/tests/helpers/solend_program_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,31 @@ impl Info<LendingMarket> {
.await
}

pub async fn donate_to_reserve(
&self,
test: &mut SolendProgramTest,
reserve: &Info<Reserve>,
user: &User,
liquidity_amount: u64,
) -> Result<(), BanksClientError> {
let instructions = [
ComputeBudgetInstruction::set_compute_unit_limit(50_000),
donate_to_reserve(
solend_program::id(),
liquidity_amount,
user.get_account(&reserve.account.liquidity.mint_pubkey)
.unwrap(),
reserve.account.liquidity.supply_pubkey,
reserve.pubkey,
self.pubkey,
user.keypair.pubkey(),
),
];

test.process_transaction(&instructions, Some(&[&user.keypair]))
.await
}

pub async fn update_reserve_config(
&self,
test: &mut SolendProgramTest,
Expand Down
48 changes: 48 additions & 0 deletions token-lending/sdk/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,22 @@ pub enum LendingInstruction {
/// Obligation is closable
closeable: bool,
},

// 24
/// DonateToReserve
///
/// 0. `[writable]` Source liquidity token account.
/// Minted by repay reserve liquidity mint.
/// $authority can transfer $liquidity_amount.
/// 1. `[writable]` Destination reserve liquidity supply SPL Token account.
/// 2. `[writable]` Repay reserve account - refreshed.
/// 3. `[]` Lending market account.
/// 4. `[signer]` User transfer authority ($authority).
/// 5. `[]` Token program id.
DonateToReserve {
/// amount to donate
liquidity_amount: u64,
},
}

impl LendingInstruction {
Expand Down Expand Up @@ -766,6 +782,10 @@ impl LendingInstruction {

Self::SetObligationCloseabilityStatus { closeable }
}
24 => {
let (liquidity_amount, _rest) = Self::unpack_u64(rest)?;
Self::DonateToReserve { liquidity_amount }
}
_ => {
msg!("Instruction cannot be unpacked");
return Err(LendingError::InstructionUnpackError.into());
Expand Down Expand Up @@ -1061,6 +1081,10 @@ impl LendingInstruction {
buf.push(23);
buf.extend_from_slice(&(closeable as u8).to_le_bytes());
}
Self::DonateToReserve { liquidity_amount } => {
buf.push(24);
buf.extend_from_slice(&liquidity_amount.to_le_bytes());
}
}
buf
}
Expand Down Expand Up @@ -1849,6 +1873,30 @@ pub fn set_obligation_closeability_status(
}
}

/// Creates a `DonateToReserve` instruction
pub fn donate_to_reserve(
program_id: Pubkey,
liquidity_amount: u64,
source_liquidity_pubkey: Pubkey,
destination_liquidity_pubkey: Pubkey,
reserve_pubkey: Pubkey,
lending_market_pubkey: Pubkey,
user_transfer_authority_pubkey: Pubkey,
) -> Instruction {
Instruction {
program_id,
accounts: vec![
AccountMeta::new(source_liquidity_pubkey, false),
AccountMeta::new(destination_liquidity_pubkey, false),
AccountMeta::new(reserve_pubkey, false),
AccountMeta::new_readonly(lending_market_pubkey, false),
AccountMeta::new_readonly(user_transfer_authority_pubkey, true),
AccountMeta::new_readonly(spl_token::id(), false),
],
data: LendingInstruction::DonateToReserve { liquidity_amount }.pack(),
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
9 changes: 9 additions & 0 deletions token-lending/sdk/src/state/reserve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,15 @@ impl ReserveLiquidity {
Ok(())
}

/// Add donate_amount to available liquidity and subtract settle amount from total borrows
pub fn donate(&mut self, donate_amount: u64) -> ProgramResult {
self.available_amount = self
.available_amount
.checked_add(donate_amount)
.ok_or(LendingError::MathOverflow)?;
Ok(())
}

/// Subtract settle amount from accumulated_protocol_fees_wads and withdraw_amount from available liquidity
pub fn redeem_fees(&mut self, withdraw_amount: u64) -> ProgramResult {
self.available_amount = self
Expand Down

0 comments on commit 9ffd16d

Please sign in to comment.