Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
68 changes: 64 additions & 4 deletions programs/drift/src/instructions/lp_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use anchor_lang::{prelude::*, Accounts, Key, Result};
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};

use crate::ids::lp_pool_swap_wallet;
use crate::math::constants::{PERCENTAGE_PRECISION, PRICE_PRECISION_I64};
use crate::math::constants::PRICE_PRECISION_I64;
use crate::math::oracle::OracleValidity;
use crate::state::events::{DepositDirection, LPBorrowLendDepositRecord};
use crate::state::paused_operations::ConstituentLpOperation;
use crate::validation::whitelist::validate_whitelist_token;
use crate::{
Expand All @@ -26,7 +27,7 @@ use crate::{
state::{
amm_cache::{AmmCacheFixed, CacheInfo, AMM_POSITIONS_CACHE},
constituent_map::{ConstituentMap, ConstituentSet},
events::{emit_stack, LPMintRedeemRecord, LPSettleRecord, LPSwapRecord},
events::{emit_stack, LPMintRedeemRecord, LPSwapRecord},
lp_pool::{
update_constituent_target_base_for_derivatives, AmmConstituentDatum,
AmmConstituentMappingFixed, Constituent, ConstituentCorrelationsFixed,
Expand Down Expand Up @@ -1369,6 +1370,8 @@ pub fn handle_deposit_to_program_vault<'c: 'info, 'info>(
)?;
let remaining_accounts = &mut ctx.remaining_accounts.iter().peekable();

let mut constituent = ctx.accounts.constituent.load_mut()?;

if amount == 0 {
return Err(ErrorCode::InsufficientDeposit.into());
}
Expand All @@ -1382,8 +1385,14 @@ pub fn handle_deposit_to_program_vault<'c: 'info, 'info>(
Some(&oracle_data),
clock.unix_timestamp,
)?;
let token_balance_after_cumulative_interest_update = constituent
.spot_balance
.get_signed_token_amount(&spot_market)?;

let interest_accrued_token_amount = token_balance_after_cumulative_interest_update
.cast::<i64>()?
.safe_sub(constituent.last_spot_balance_token_amount)?;

let mut constituent = ctx.accounts.constituent.load_mut()?;
if constituent.last_oracle_slot < oracle_data_slot {
constituent.last_oracle_price = oracle_data.price;
constituent.last_oracle_slot = oracle_data_slot;
Expand Down Expand Up @@ -1454,6 +1463,27 @@ pub fn handle_deposit_to_program_vault<'c: 'info, 'info>(
"Constituent balance mismatch after withdraw from program vault"
)?;

let new_token_balance = constituent
.spot_balance
.get_signed_token_amount(&spot_market)?
.cast::<i64>()?;

emit!(LPBorrowLendDepositRecord {
ts: clock.unix_timestamp,
slot: clock.slot,
spot_market_index: spot_market.market_index,
constituent_index: constituent.constituent_index,
direction: DepositDirection::Deposit,
token_balance: new_token_balance,
last_token_balance: constituent.last_spot_balance_token_amount,
interest_accrued_token_amount,
amount_deposit_withdraw: amount,
});
constituent.last_spot_balance_token_amount = new_token_balance;
constituent.cumulative_spot_interest_accrued_token_amount = constituent
.cumulative_spot_interest_accrued_token_amount
.safe_add(interest_accrued_token_amount)?;

Ok(())
}

Expand All @@ -1474,19 +1504,28 @@ pub fn handle_withdraw_from_program_vault<'c: 'info, 'info>(
)?;
let remaining_accounts = &mut ctx.remaining_accounts.iter().peekable();

let mut constituent = ctx.accounts.constituent.load_mut()?;

if amount == 0 {
return Err(ErrorCode::InsufficientDeposit.into());
}

let oracle_data = oracle_map.get_price_data(&oracle_id)?;
let oracle_data_slot = clock.slot - oracle_data.delay.max(0i64).cast::<u64>()?;

controller::spot_balance::update_spot_market_cumulative_interest(
&mut spot_market,
Some(&oracle_data),
clock.unix_timestamp,
)?;
let token_balance_after_cumulative_interest_update = constituent
.spot_balance
.get_signed_token_amount(&spot_market)?;

let interest_accrued_token_amount = token_balance_after_cumulative_interest_update
.cast::<i64>()?
.safe_sub(constituent.last_spot_balance_token_amount)?;

let mut constituent = ctx.accounts.constituent.load_mut()?;
if constituent.last_oracle_slot < oracle_data_slot {
constituent.last_oracle_price = oracle_data.price;
constituent.last_oracle_slot = oracle_data_slot;
Expand All @@ -1507,6 +1546,27 @@ pub fn handle_withdraw_from_program_vault<'c: 'info, 'info>(
Some(remaining_accounts),
)?;

let new_token_balance = constituent
.spot_balance
.get_signed_token_amount(&spot_market)?
.cast::<i64>()?;

emit!(LPBorrowLendDepositRecord {
ts: clock.unix_timestamp,
slot: clock.slot,
spot_market_index: spot_market.market_index,
constituent_index: constituent.constituent_index,
direction: DepositDirection::Withdraw,
token_balance: new_token_balance,
last_token_balance: constituent.last_spot_balance_token_amount,
interest_accrued_token_amount,
amount_deposit_withdraw: amount,
});
constituent.last_spot_balance_token_amount = new_token_balance;
constituent.cumulative_spot_interest_accrued_token_amount = constituent
.cumulative_spot_interest_accrued_token_amount
.safe_add(interest_accrued_token_amount)?;

Ok(())
}

Expand Down
8 changes: 4 additions & 4 deletions programs/drift/src/state/constituent_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ impl<'a> ConstituentMap<'a> {
"Constituent lp pool pubkey does not match lp pool pubkey"
)?;

// constituent index 276 bytes from front of account
let constituent_index = u16::from_le_bytes(*array_ref![data, 292, 2]);
// constituent index 308 bytes from front of account
let constituent_index = u16::from_le_bytes(*array_ref![data, 308, 2]);
if constituent_map.0.contains_key(&constituent_index) {
msg!(
"Can not include same constituent index twice {}",
Expand Down Expand Up @@ -183,7 +183,7 @@ impl<'a> ConstituentMap<'a> {
}

// market index 1160 bytes from front of account
let constituent_index = u16::from_le_bytes(*array_ref![data, 292, 2]);
let constituent_index = u16::from_le_bytes(*array_ref![data, 308, 2]);

let is_writable = account_info.is_writable;
let account_loader: AccountLoader<Constituent> =
Expand Down Expand Up @@ -221,7 +221,7 @@ impl<'a> ConstituentMap<'a> {
return Err(ErrorCode::ConstituentCouldNotLoad);
}

let constituent_index = u16::from_le_bytes(*array_ref![data, 292, 2]);
let constituent_index = u16::from_le_bytes(*array_ref![data, 308, 2]);

if constituent_map.0.contains_key(&constituent_index) {
msg!(
Expand Down
18 changes: 18 additions & 0 deletions programs/drift/src/state/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -846,3 +846,21 @@ pub struct LPMintRedeemRecord {
impl Size for LPMintRedeemRecord {
const SIZE: usize = 328;
}

#[event]
#[derive(Default)]
pub struct LPBorrowLendDepositRecord {
pub ts: i64,
pub slot: u64,
pub spot_market_index: u16,
pub constituent_index: u16,
pub direction: DepositDirection,
pub token_balance: i64,
pub last_token_balance: i64,
pub interest_accrued_token_amount: i64,
pub amount_deposit_withdraw: u64,
}

impl Size for LPBorrowLendDepositRecord {
const SIZE: usize = 72;
}
5 changes: 4 additions & 1 deletion programs/drift/src/state/lp_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,9 @@ pub struct Constituent {
/// spot borrow-lend balance for constituent
pub spot_balance: ConstituentSpotBalance, // should be in constituent base asset

pub last_spot_balance_token_amount: i64, // token precision
pub cumulative_spot_interest_accrued_token_amount: i64, // token precision

/// max deviation from target_weight allowed for the constituent
/// precision: PERCENTAGE_PRECISION
pub max_weight_deviation: i64,
Expand Down Expand Up @@ -844,7 +847,7 @@ pub struct Constituent {
}

impl Size for Constituent {
const SIZE: usize = 304;
const SIZE: usize = 320;
}

#[derive(BitFlags, Clone, Copy, PartialEq, Debug, Eq)]
Expand Down
87 changes: 47 additions & 40 deletions programs/drift/src/state/lp_pool/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1047,14 +1047,14 @@ mod swap_tests {
#[test]
fn test_get_remove_liquidity_mint_amount_with_existing_aum_6_decimals_large_aum() {
get_remove_liquidity_mint_amount_scenario(
100_000_000_000 * 1_000_000, // last_aum ($100,000,000,000)
0, // now
6, // in_decimals
100_000_000_000 * 1_000_000, // in_amount
100_000_000_000 * 1_000_000, // dlp_total_supply
99990000000000000, // expected_out_amount
10000000000000, // expected_lp_fee
349765020000000, // expected_out_fee_amount
100_000_000_000 * 1_000_000, // last_aum ($100,000,000,000)
0, // now
6, // in_decimals
100_000_000_000 * 1_000_000 - 1_000_000, // Leave in QUOTE AMOUNT
100_000_000_000 * 1_000_000, // dlp_total_supply
99989999900000000, // expected_out_amount
9999999999900, // expected_lp_fee
349765019650200, // expected_out_fee_amount
1,
2,
2,
Expand All @@ -1065,14 +1065,14 @@ mod swap_tests {
#[test]
fn test_get_remove_liquidity_mint_amount_with_existing_aum_8_decimals_large_aum() {
get_remove_liquidity_mint_amount_scenario(
10_000_000_000_000_000, // last_aum ($10,000,000,000)
0, // now
8, // in_decimals
10_000_000_000 * 100_000_000, // in_amount
10_000_000_000 * 1_000_000, // dlp_total_supply
9_999_000_000_000_000_0000, // expected_out_amount
100000000000000, // expected_lp_fee
3764623500000000000, // expected_out_fee_amount
10_000_000_000_000_000, // last_aum ($10,000,000,000)
0, // now
8, // in_decimals
10_000_000_000 * 1_000_000 - 1_000_000, // in_amount
10_000_000_000 * 1_000_000, // dlp_total_supply
999899999000000000, // expected_out_amount
(10_000_000_000 * 1_000_000 - 1_000_000) / 10000, // expected_lp_fee
3497650196502000, // expected_out_fee_amount
1,
2,
2,
Expand Down Expand Up @@ -1540,6 +1540,7 @@ mod swap_fee_tests {

#[cfg(test)]
mod settle_tests {
use crate::math::constants::{QUOTE_PRECISION, QUOTE_PRECISION_I64, QUOTE_PRECISION_U64};
use crate::math::lp_pool::perp_lp_pool_settlement::{
calculate_settlement_amount, update_cache_info, SettlementContext, SettlementDirection,
SettlementResult,
Expand Down Expand Up @@ -1570,35 +1571,38 @@ mod settle_tests {
#[test]
fn test_lp_to_perp_settlement_sufficient_balance() {
let ctx = SettlementContext {
quote_owed_from_lp: 500,
quote_constituent_token_balance: 1000,
fee_pool_balance: 300,
pnl_pool_balance: 200,
quote_owed_from_lp: 500 * QUOTE_PRECISION_I64,
quote_constituent_token_balance: 1000 * QUOTE_PRECISION_U64,
fee_pool_balance: 300 * QUOTE_PRECISION,
pnl_pool_balance: 200 * QUOTE_PRECISION,
quote_market: &create_mock_spot_market(),
max_settle_quote_amount: 10000,
max_settle_quote_amount: 10000 * QUOTE_PRECISION_U64,
};

let result = calculate_settlement_amount(&ctx).unwrap();
assert_eq!(result.direction, SettlementDirection::FromLpPool);
assert_eq!(result.amount_transferred, 500);
assert_eq!(result.amount_transferred, 500 * QUOTE_PRECISION_U64);
assert_eq!(result.fee_pool_used, 0);
assert_eq!(result.pnl_pool_used, 0);
}

#[test]
fn test_lp_to_perp_settlement_insufficient_balance() {
let ctx = SettlementContext {
quote_owed_from_lp: 1500,
quote_constituent_token_balance: 1000,
fee_pool_balance: 300,
pnl_pool_balance: 200,
quote_owed_from_lp: 1500 * QUOTE_PRECISION_I64,
quote_constituent_token_balance: 1000 * QUOTE_PRECISION_U64,
fee_pool_balance: 300 * QUOTE_PRECISION,
pnl_pool_balance: 200 * QUOTE_PRECISION,
quote_market: &create_mock_spot_market(),
max_settle_quote_amount: 10000,
max_settle_quote_amount: 10000 * QUOTE_PRECISION_U64,
};

let result = calculate_settlement_amount(&ctx).unwrap();
assert_eq!(result.direction, SettlementDirection::FromLpPool);
assert_eq!(result.amount_transferred, 1000); // Limited by LP balance
assert_eq!(
result.amount_transferred,
1000 * QUOTE_PRECISION_U64 - QUOTE_PRECISION_U64
); // Limited by LP balance
}

#[test]
Expand Down Expand Up @@ -1809,17 +1813,20 @@ mod settle_tests {
fn test_exact_boundary_settlements() {
// Test when quote_owed exactly equals LP balance
let ctx = SettlementContext {
quote_owed_from_lp: 1000,
quote_constituent_token_balance: 1000,
fee_pool_balance: 500,
pnl_pool_balance: 300,
quote_owed_from_lp: 1000 * QUOTE_PRECISION_I64,
quote_constituent_token_balance: 1000 * QUOTE_PRECISION_U64,
fee_pool_balance: 500 * QUOTE_PRECISION,
pnl_pool_balance: 300 * QUOTE_PRECISION,
quote_market: &create_mock_spot_market(),
max_settle_quote_amount: 10000,
max_settle_quote_amount: 10000 * QUOTE_PRECISION_U64,
};

let result = calculate_settlement_amount(&ctx).unwrap();
assert_eq!(result.direction, SettlementDirection::FromLpPool);
assert_eq!(result.amount_transferred, 1000);
assert_eq!(
result.amount_transferred,
1000 * QUOTE_PRECISION_U64 - QUOTE_PRECISION_U64
); // Leave QUOTE PRECISION

// Test when negative quote_owed exactly equals total pool balance
let ctx = SettlementContext {
Expand Down Expand Up @@ -1852,7 +1859,7 @@ mod settle_tests {

let result = calculate_settlement_amount(&ctx).unwrap();
assert_eq!(result.direction, SettlementDirection::FromLpPool);
assert_eq!(result.amount_transferred, 1);
assert_eq!(result.amount_transferred, 0); // Cannot transfer if less than QUOTE_PRECISION

// Test with minimal negative amount
let ctx = SettlementContext {
Expand Down Expand Up @@ -2052,17 +2059,17 @@ mod settle_tests {
#[test]
fn test_lp_to_perp_capped_with_max() {
let ctx = SettlementContext {
quote_owed_from_lp: 1100,
quote_constituent_token_balance: 2000,
quote_owed_from_lp: 1100 * QUOTE_PRECISION_I64,
quote_constituent_token_balance: 2000 * QUOTE_PRECISION_U64,
fee_pool_balance: 0, // No fee pool
pnl_pool_balance: 1200,
pnl_pool_balance: 1200 * QUOTE_PRECISION,
quote_market: &create_mock_spot_market(),
max_settle_quote_amount: 1000,
max_settle_quote_amount: 1000 * QUOTE_PRECISION_U64,
};

let result = calculate_settlement_amount(&ctx).unwrap();
assert_eq!(result.direction, SettlementDirection::FromLpPool);
assert_eq!(result.amount_transferred, 1000);
assert_eq!(result.amount_transferred, 1000 * QUOTE_PRECISION_U64); // Leave QUOTE PRECISION in the balance
assert_eq!(result.fee_pool_used, 0);
assert_eq!(result.pnl_pool_used, 0);
}
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/driftClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9250,7 +9250,7 @@ export class DriftClient {

public async updateUserGovTokenInsuranceStake(
authority: PublicKey,
txParams?: TxParams,
txParams?: TxParams
): Promise<TransactionSignature> {
const ix = await this.getUpdateUserGovTokenInsuranceStakeIx(authority);
const tx = await this.buildTransaction(ix, txParams);
Expand Down
4 changes: 3 additions & 1 deletion sdk/src/events/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
LPMintRedeemRecord,
LPSettleRecord,
LPSwapRecord,
LPBorrowLendDepositRecord,
} from '../types';
import { EventEmitter } from 'events';

Expand Down Expand Up @@ -147,7 +148,8 @@ export type DriftEvent =
| Event<TransferProtocolIfSharesToRevenuePoolRecord>
| Event<LPSettleRecord>
| Event<LPMintRedeemRecord>
| Event<LPSwapRecord>;
| Event<LPSwapRecord>
| Event<LPBorrowLendDepositRecord>;

export interface EventSubscriberEvents {
newEvent: (event: WrappedEvent<EventType>) => void;
Expand Down
Loading