Skip to content

derivative constituents + better testing + bug fixes #1657

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

Merged
merged 4 commits into from
Jun 10, 2025
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
5 changes: 5 additions & 0 deletions programs/drift/src/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ pub mod lighthouse {
use solana_program::declare_id;
declare_id!("L2TExMFKdjpN9kozasaurPirfHy9P8sbXoAN1qA3S95");
}

pub mod usdc_mint {
use solana_program::declare_id;
declare_id!("BJE5MMbqXjVwjAF7oxwPYXnTXDyspzZyt4vwenNw5ruG");
}
42 changes: 31 additions & 11 deletions programs/drift/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::controller::token::{close_vault, receive, send_from_program_vault};
use crate::error::ErrorCode;
use crate::ids::{
admin_hot_wallet, jupiter_mainnet_3, jupiter_mainnet_4, jupiter_mainnet_6, lighthouse,
marinade_mainnet, serum_program,
marinade_mainnet, serum_program, usdc_mint,
};
use crate::instructions::constraints::*;
use crate::instructions::optional_accounts::{load_maps, AccountMaps};
Expand Down Expand Up @@ -4515,7 +4515,8 @@ pub fn handle_initialize_lp_pool(
max_mint_fee_premium: max_mint_fee,
revenue_rebalance_period,
next_mint_redeem_id: 1,
_padding: [0; 12],
usdc_consituent_index: 0,
_padding: [0; 10],
};

let amm_constituent_mapping = &mut ctx.accounts.amm_constituent_mapping;
Expand Down Expand Up @@ -4739,7 +4740,8 @@ pub fn handle_initialize_constituent<'info>(
swap_fee_max: i64,
oracle_staleness_threshold: u64,
cost_to_trade_bps: i32,
stablecoin_weight: u64,
constituent_derivative_index: Option<i16>,
derivative_weight: u64,
) -> Result<()> {
let mut constituent = ctx.accounts.constituent.load_init()?;
let mut lp_pool = ctx.accounts.lp_pool.load_mut()?;
Expand All @@ -4765,7 +4767,7 @@ pub fn handle_initialize_constituent<'info>(
);

validate!(
stablecoin_weight <= PRICE_PRECISION_U64,
derivative_weight <= PRICE_PRECISION_U64,
ErrorCode::InvalidConstituent,
"stablecoin_weight must be between 0 and 1",
)?;
Expand All @@ -4784,9 +4786,14 @@ pub fn handle_initialize_constituent<'info>(
constituent.lp_pool = lp_pool.pubkey;
constituent.constituent_index = (constituent_target_base.targets.len() - 1) as u16;
constituent.next_swap_id = 1;
constituent.stablecoin_weight = stablecoin_weight;
constituent.constituent_derivative_index = constituent_derivative_index.unwrap_or(-1);
constituent.derivative_weight = derivative_weight;
lp_pool.constituents += 1;

if constituent.mint.eq(&usdc_mint::ID) {
lp_pool.usdc_consituent_index = constituent.constituent_index;
}

Ok(())
}

Expand All @@ -4797,7 +4804,8 @@ pub struct ConstituentParams {
pub swap_fee_max: Option<i64>,
pub oracle_staleness_threshold: Option<u64>,
pub cost_to_trade_bps: Option<i32>,
pub stablecoin_weight: Option<u64>,
pub constituent_derivative_index: Option<i16>,
pub derivative_weight: Option<u64>,
}

pub fn handle_update_constituent_params<'info>(
Expand Down Expand Up @@ -4858,13 +4866,23 @@ pub fn handle_update_constituent_params<'info>(
target.cost_to_trade_bps = constituent_params.cost_to_trade_bps.unwrap();
}

if constituent_params.stablecoin_weight.is_some() {
if constituent_params.derivative_weight.is_some() {
msg!(
"derivative_weight: {:?} -> {:?}",
constituent.derivative_weight,
constituent_params.derivative_weight
);
constituent.derivative_weight = constituent_params.derivative_weight.unwrap();
}

if constituent_params.constituent_derivative_index.is_some() {
msg!(
"stablecoin_weight: {:?} -> {:?}",
constituent.stablecoin_weight,
constituent_params.stablecoin_weight
"constituent_derivative_index: {:?} -> {:?}",
constituent.constituent_derivative_index,
constituent_params.constituent_derivative_index
);
constituent.stablecoin_weight = constituent_params.stablecoin_weight.unwrap();
constituent.constituent_derivative_index =
constituent_params.constituent_derivative_index.unwrap();
}

Ok(())
Expand Down Expand Up @@ -4965,6 +4983,8 @@ pub fn handle_add_amm_constituent_data<'info>(
let mut datum = AmmConstituentDatum::default();
datum.perp_market_index = perp_market_index;
datum.constituent_index = constituent_index;
datum.weight = init_datum.weight;
datum.last_slot = Clock::get()?.slot;

// Check if the datum already exists
let exists = amm_mapping.weights.iter().any(|d| {
Expand Down
164 changes: 137 additions & 27 deletions programs/drift/src/instructions/lp_pool.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use std::collections::BTreeMap;

use anchor_lang::{prelude::*, Accounts, Key, Result};
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};

use crate::{
controller::token::{burn_tokens, mint_tokens},
controller::{
lp,
token::{burn_tokens, mint_tokens},
},
error::ErrorCode,
get_then_update_id,
math::{
casting::Cast,
constants::{PERCENTAGE_PRECISION_I128, PRICE_PRECISION_I128},
constants::{
BASE_PRECISION_I128, PERCENTAGE_PRECISION_I128, PERCENTAGE_PRECISION_I64,
PERCENTAGE_PRECISION_U64, PRICE_PRECISION_I128, QUOTE_PRECISION_I128,
},
oracle::{is_oracle_valid_for_action, oracle_validity, DriftAction},
safe_math::SafeMath,
},
Expand All @@ -16,7 +24,7 @@ use crate::{
constituent_map::{ConstituentMap, ConstituentSet},
events::{LPMintRedeemRecord, LPSwapRecord},
lp_pool::{
AmmConstituentDatum, AmmConstituentMappingFixed, Constituent,
calculate_target_weight, AmmConstituentDatum, AmmConstituentMappingFixed, Constituent,
ConstituentTargetBaseFixed, LPPool, TargetsDatum, WeightValidationFlags,
},
oracle::OraclePriceData,
Expand Down Expand Up @@ -167,15 +175,19 @@ pub fn handle_update_constituent_target_base<'c: 'info, 'info>(
let constituent_map =
ConstituentMap::load(&ConstituentSet::new(), &lp_pool_key, remaining_accounts)?;

let mut constituent_indexes_and_prices: Vec<(u16, i64)> = vec![];
let mut constituent_indexes_and_decimals_and_prices: Vec<(u16, u8, i64)> = vec![];
for (index, loader) in &constituent_map.0 {
let constituent_ref = loader.load()?;
constituent_indexes_and_prices.push((*index, constituent_ref.last_oracle_price));
constituent_indexes_and_decimals_and_prices.push((
*index,
constituent_ref.decimals,
constituent_ref.last_oracle_price,
));
}

let exists_invalid_constituent_index = constituent_indexes_and_prices
let exists_invalid_constituent_index = constituent_indexes_and_decimals_and_prices
.iter()
.any(|(index, _)| *index as u32 >= num_constituents);
.any(|(index, _, _)| *index as u32 >= num_constituents);

validate!(
!exists_invalid_constituent_index,
Expand All @@ -186,7 +198,7 @@ pub fn handle_update_constituent_target_base<'c: 'info, 'info>(
constituent_target_base.update_target_base(
&amm_constituent_mapping,
amm_inventories.as_slice(),
constituent_indexes_and_prices.as_slice(),
constituent_indexes_and_decimals_and_prices.as_slice(),
slot,
)?;

Expand Down Expand Up @@ -248,9 +260,22 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
let mut aum: u128 = 0;
let mut crypto_delta = 0_i128;
let mut oldest_slot = u64::MAX;
let mut stablecoin_constituent_indexes: Vec<usize> = vec![];
let mut derivative_groups: BTreeMap<u16, Vec<u16>> = BTreeMap::new();
for i in 0..lp_pool.constituents as usize {
let mut constituent = constituent_map.get_ref_mut(&(i as u16))?;
if constituent.constituent_derivative_index >= 0 && constituent.derivative_weight != 0 {
if !derivative_groups.contains_key(&(constituent.constituent_derivative_index as u16)) {
derivative_groups.insert(
constituent.constituent_derivative_index as u16,
vec![constituent.constituent_index],
);
} else {
derivative_groups
.get_mut(&(constituent.constituent_derivative_index as u16))
.unwrap()
.push(constituent.constituent_index);
}
}

let spot_market = spot_market_map.get_ref(&constituent.spot_market_index)?;

Expand Down Expand Up @@ -305,10 +330,21 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
.safe_mul(oracle_price.unwrap() as i128)?
.safe_div(PRICE_PRECISION_I128)?
.max(0);
if constituent.stablecoin_weight == 0 {
crypto_delta = crypto_delta.safe_add(constituent_aum.cast()?)?;
} else {
stablecoin_constituent_indexes.push(i);
msg!(
"constituent: {}, aum: {}, deriv index: {}",
constituent.constituent_index,
constituent_aum,
constituent.constituent_derivative_index
);
if constituent.constituent_index != lp_pool.usdc_consituent_index
&& constituent.constituent_derivative_index != lp_pool.usdc_consituent_index as i16
{
let constituent_target_notional = constituent_target_base
.get(constituent.constituent_index as u32)
.target_base
.safe_mul(constituent.last_oracle_price)?
.safe_div(10_i64.pow(spot_market.decimals as u32))?;
crypto_delta = crypto_delta.safe_add(constituent_target_notional.cast()?)?;
}
aum = aum.safe_add(constituent_aum.cast()?)?;
}
Expand All @@ -318,14 +354,82 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
lp_pool.last_aum_slot = slot;
lp_pool.last_aum_ts = Clock::get()?.unix_timestamp;

let total_stable_target_base = aum.cast::<i128>()?.safe_sub(crypto_delta.abs())?;
for index in stablecoin_constituent_indexes {
let constituent = constituent_map.get_ref(&(index as u16))?;
let stable_target = constituent_target_base.get_mut(index as u32);
stable_target.target_base = total_stable_target_base
.safe_mul(constituent.stablecoin_weight as i128)?
.safe_div(PERCENTAGE_PRECISION_I128)?
.cast::<i64>()?;
// Set USDC stable weight
let total_stable_target_base = aum
.cast::<i128>()?
.safe_sub(crypto_delta.abs())?
.max(0_i128);
constituent_target_base
.get_mut(lp_pool.usdc_consituent_index as u32)
.target_base = total_stable_target_base
.safe_mul(
10_i128.pow(
constituent_map
.get_ref(&lp_pool.usdc_consituent_index)?
.decimals as u32,
),
)?
.safe_div(QUOTE_PRECISION_I128)?
.cast::<i64>()?;

msg!(
"stable target base: {}",
constituent_target_base
.get(lp_pool.usdc_consituent_index as u32)
.target_base
);
msg!("aum: {}, crypto_delta: {}", aum, crypto_delta);
msg!("derivative groups: {:?}", derivative_groups);

// Handle all other derivatives
for (parent_index, constituent_indexes) in derivative_groups.iter() {
let parent_constituent = constituent_map.get_ref(&(parent_index))?;
let parent_target_base = constituent_target_base
.get(*parent_index as u32)
.target_base;
let target_parent_weight = calculate_target_weight(
parent_target_base,
&*spot_market_map.get_ref(&parent_constituent.spot_market_index)?,
parent_constituent.last_oracle_price,
aum,
WeightValidationFlags::NONE,
)?;
let mut derivative_weights_sum = 0;
for constituent_index in constituent_indexes {
let constituent = constituent_map.get_ref(constituent_index)?;
derivative_weights_sum += constituent.derivative_weight;

let target_weight = target_parent_weight
.safe_mul(constituent.derivative_weight as i64)?
.safe_div(PERCENTAGE_PRECISION_I64)?;

msg!(
"constituent: {}, target weight: {}",
constituent_index,
target_weight,
);
let target_base = lp_pool
.last_aum
.cast::<i128>()?
.safe_mul(target_weight as i128)?
.safe_div(PERCENTAGE_PRECISION_I128)?
.safe_mul(10_i128.pow(constituent.decimals as u32))?
.safe_div(constituent.last_oracle_price as i128)?;

msg!(
"constituent: {}, target base: {}",
constituent_index,
target_base
);
constituent_target_base
.get_mut(*constituent_index as u32)
.target_base = target_base.cast::<i64>()?;
}
constituent_target_base
.get_mut(*parent_index as u32)
.target_base = parent_target_base
.safe_mul(PERCENTAGE_PRECISION_U64.safe_sub(derivative_weights_sum)? as i64)?
.safe_div(PERCENTAGE_PRECISION_I64)?;
}

Ok(())
Expand Down Expand Up @@ -611,12 +715,17 @@ pub fn handle_lp_pool_add_liquidity<'c: 'info, 'info>(

update_spot_market_cumulative_interest(&mut in_spot_market, Some(&in_oracle), now)?;

let in_target_weight = constituent_target_base.get_target_weight(
in_constituent.constituent_index,
&in_spot_market,
in_oracle.price,
lp_pool.last_aum, // TODO: add in_amount * in_oracle to est post add_liquidity aum
)?;
msg!("aum: {}", lp_pool.last_aum);
let in_target_weight = if lp_pool.last_aum == 0 {
PERCENTAGE_PRECISION_I64 // 100% weight if no aum
} else {
constituent_target_base.get_target_weight(
in_constituent.constituent_index,
&in_spot_market,
in_oracle.price,
lp_pool.last_aum, // TODO: add in_amount * in_oracle to est post add_liquidity aum
)?
};

let dlp_total_supply = ctx.accounts.lp_mint.supply;

Expand Down Expand Up @@ -973,6 +1082,7 @@ pub struct UpdateConstituentTargetBase<'info> {
/// CHECK: checked in AmmConstituentMappingZeroCopy checks
pub amm_constituent_mapping: AccountInfo<'info>,
/// CHECK: checked in ConstituentTargetBaseZeroCopy checks
#[account(mut)]
pub constituent_target_base: AccountInfo<'info>,
/// CHECK: checked in AmmCacheZeroCopy checks
pub amm_cache: AccountInfo<'info>,
Expand Down
6 changes: 4 additions & 2 deletions programs/drift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1746,7 +1746,8 @@ pub mod drift {
swap_fee_max: i64,
oracle_staleness_threshold: u64,
cost_to_trade: i32,
stablecoin_weight: u64,
constituent_derivative_index: Option<i16>,
derivative_weight: u64,
) -> Result<()> {
handle_initialize_constituent(
ctx,
Expand All @@ -1757,7 +1758,8 @@ pub mod drift {
swap_fee_max,
oracle_staleness_threshold,
cost_to_trade,
stablecoin_weight,
constituent_derivative_index,
derivative_weight,
)
}

Expand Down
Loading