Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

donate to reserve function #194

Merged
merged 8 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
63 changes: 63 additions & 0 deletions token-lending/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,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 +3191,65 @@ 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());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing reserve whitelist

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());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typically we also check this, but i don't think it's strictly necessary:

 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());
    }

_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
Loading