Skip to content

Commit 19c88f8

Browse files
authored
refactor update_aum, add unit tests (#1727)
* refactor update_aum, add unit tests * add constituent target base tests * update doc
1 parent 464e1d3 commit 19c88f8

File tree

7 files changed

+1466
-172
lines changed

7 files changed

+1466
-172
lines changed

programs/drift/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,8 @@ pub enum ErrorCode {
664664
ConstituentOracleStale,
665665
#[msg("LP Invariant failed")]
666666
LpInvariantFailed,
667+
#[msg("Invalid constituent derivative weights")]
668+
InvalidConstituentDerivativeWeights,
667669
}
668670

669671
#[macro_export]

programs/drift/src/instructions/lp_pool.rs

Lines changed: 21 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::collections::BTreeMap;
2-
31
use anchor_lang::{prelude::*, Accounts, Key, Result};
42
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
53

@@ -15,10 +13,7 @@ use crate::{
1513
math::{
1614
self,
1715
casting::Cast,
18-
constants::{
19-
PERCENTAGE_PRECISION, PERCENTAGE_PRECISION_I128, PERCENTAGE_PRECISION_I64,
20-
PERCENTAGE_PRECISION_U64, PRICE_PRECISION, PRICE_PRECISION_I128, QUOTE_PRECISION_I128,
21-
},
16+
constants::{PERCENTAGE_PRECISION_I64, PRICE_PRECISION},
2217
oracle::{is_oracle_valid_for_action, oracle_validity, DriftAction},
2318
safe_math::SafeMath,
2419
},
@@ -27,9 +22,9 @@ use crate::{
2722
constituent_map::{ConstituentMap, ConstituentSet},
2823
events::{emit_stack, LPMintRedeemRecord, LPSwapRecord},
2924
lp_pool::{
30-
calculate_target_weight, AmmConstituentDatum, AmmConstituentMappingFixed, Constituent,
31-
ConstituentCorrelationsFixed, ConstituentTargetBaseFixed, LPPool, TargetsDatum,
32-
WeightValidationFlags, LP_POOL_SWAP_AUM_UPDATE_DELAY,
25+
update_constituent_target_base_for_derivatives, AmmConstituentDatum,
26+
AmmConstituentMappingFixed, Constituent, ConstituentCorrelationsFixed,
27+
ConstituentTargetBaseFixed, LPPool, TargetsDatum, LP_POOL_SWAP_AUM_UPDATE_DELAY,
3328
MAX_AMM_CACHE_STALENESS_FOR_TARGET_CALC,
3429
},
3530
oracle::OraclePriceData,
@@ -199,6 +194,7 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
199194
let state = &ctx.accounts.state;
200195

201196
let slot = Clock::get()?.slot;
197+
let now = Clock::get()?.unix_timestamp;
202198

203199
let remaining_accounts = &mut ctx.remaining_accounts.iter().peekable();
204200

@@ -251,88 +247,14 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
251247
"Amm cache PDA does not match expected PDA"
252248
)?;
253249

254-
let mut aum: u128 = 0;
255-
let mut crypto_delta = 0_i128;
256-
let mut oldest_slot = u64::MAX;
257-
let mut derivative_groups: BTreeMap<u16, Vec<u16>> = BTreeMap::new();
258-
for i in 0..lp_pool.constituents as usize {
259-
let constituent = constituent_map.get_ref(&(i as u16))?;
260-
if slot.saturating_sub(constituent.last_oracle_slot)
261-
> constituent.oracle_staleness_threshold
262-
{
263-
msg!(
264-
"Constituent {} oracle slot is too stale: {}, current slot: {}",
265-
constituent.constituent_index,
266-
constituent.last_oracle_slot,
267-
slot
268-
);
269-
return Err(ErrorCode::ConstituentOracleStale.into());
270-
}
271-
272-
if constituent.constituent_derivative_index >= 0 && constituent.derivative_weight != 0 {
273-
if !derivative_groups.contains_key(&(constituent.constituent_derivative_index as u16)) {
274-
derivative_groups.insert(
275-
constituent.constituent_derivative_index as u16,
276-
vec![constituent.constituent_index],
277-
);
278-
} else {
279-
derivative_groups
280-
.get_mut(&(constituent.constituent_derivative_index as u16))
281-
.unwrap()
282-
.push(constituent.constituent_index);
283-
}
284-
}
285-
286-
let spot_market = spot_market_map.get_ref(&constituent.spot_market_index)?;
287-
288-
let oracle_slot = constituent.last_oracle_slot;
289-
290-
if oracle_slot < oldest_slot {
291-
oldest_slot = oracle_slot;
292-
}
293-
294-
let (numerator_scale, denominator_scale) = if spot_market.decimals > 6 {
295-
(10_i128.pow(spot_market.decimals - 6), 1)
296-
} else {
297-
(1, 10_i128.pow(6 - spot_market.decimals))
298-
};
299-
300-
let constituent_aum = constituent
301-
.get_full_balance(&spot_market)?
302-
.safe_mul(numerator_scale)?
303-
.safe_div(denominator_scale)?
304-
.safe_mul(constituent.last_oracle_price as i128)?
305-
.safe_div(PRICE_PRECISION_I128)?
306-
.max(0);
307-
msg!(
308-
"constituent: {}, aum: {}, deriv index: {}",
309-
constituent.constituent_index,
310-
constituent_aum,
311-
constituent.constituent_derivative_index
312-
);
313-
if constituent.constituent_index != lp_pool.usdc_consituent_index
314-
&& constituent.constituent_derivative_index != lp_pool.usdc_consituent_index as i16
315-
{
316-
let constituent_target_notional = constituent_target_base
317-
.get(constituent.constituent_index as u32)
318-
.target_base
319-
.safe_mul(constituent.last_oracle_price)?
320-
.safe_div(10_i64.pow(constituent.decimals as u32))?;
321-
crypto_delta = crypto_delta.safe_add(constituent_target_notional.cast()?)?;
322-
}
323-
aum = aum.safe_add(constituent_aum.cast()?)?;
324-
}
325-
326-
let mut aum_i128 = aum.cast::<i128>()?;
327-
for cache_datum in amm_cache.iter() {
328-
aum_i128 -= cache_datum.quote_owed_from_lp_pool as i128;
329-
}
330-
aum = aum_i128.max(0i128).cast::<u128>()?;
331-
332-
lp_pool.oldest_oracle_slot = oldest_slot;
333-
lp_pool.last_aum = aum;
334-
lp_pool.last_aum_slot = slot;
335-
lp_pool.last_aum_ts = Clock::get()?.unix_timestamp;
250+
let (aum, crypto_delta, derivative_groups) = lp_pool.update_aum(
251+
now,
252+
slot,
253+
&constituent_map,
254+
&spot_market_map,
255+
&constituent_target_base,
256+
&amm_cache,
257+
)?;
336258

337259
// Set USDC stable weight
338260
let total_stable_target_base = aum
@@ -341,16 +263,7 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
341263
.max(0_i128);
342264
constituent_target_base
343265
.get_mut(lp_pool.usdc_consituent_index as u32)
344-
.target_base = total_stable_target_base
345-
.safe_mul(
346-
10_i128.pow(
347-
constituent_map
348-
.get_ref(&lp_pool.usdc_consituent_index)?
349-
.decimals as u32,
350-
),
351-
)?
352-
.safe_div(QUOTE_PRECISION_I128)?
353-
.cast::<i64>()?;
266+
.target_base = total_stable_target_base.cast::<i64>()?;
354267

355268
msg!(
356269
"stable target base: {}",
@@ -361,75 +274,13 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
361274
msg!("aum: {}, crypto_delta: {}", aum, crypto_delta);
362275
msg!("derivative groups: {:?}", derivative_groups);
363276

364-
// Handle all other derivatives
365-
for (parent_index, constituent_indexes) in derivative_groups.iter() {
366-
let parent_constituent = constituent_map.get_ref(&(parent_index))?;
367-
let parent_target_base = constituent_target_base
368-
.get(*parent_index as u32)
369-
.target_base;
370-
let target_parent_weight = calculate_target_weight(
371-
parent_target_base,
372-
&*spot_market_map.get_ref(&parent_constituent.spot_market_index)?,
373-
parent_constituent.last_oracle_price,
374-
aum,
375-
WeightValidationFlags::NONE,
376-
)?;
377-
let mut derivative_weights_sum = 0;
378-
for constituent_index in constituent_indexes {
379-
let constituent = constituent_map.get_ref(constituent_index)?;
380-
if constituent.last_oracle_price
381-
< parent_constituent
382-
.last_oracle_price
383-
.safe_mul(constituent.constituent_derivative_depeg_threshold as i64)?
384-
.safe_div(PERCENTAGE_PRECISION_I64)?
385-
{
386-
msg!(
387-
"Constituent {} last oracle price {} is too low compared to parent constituent {} last oracle price {}. Assuming depegging and setting target base to 0.",
388-
constituent.constituent_index,
389-
constituent.last_oracle_price,
390-
parent_constituent.constituent_index,
391-
parent_constituent.last_oracle_price
392-
);
393-
constituent_target_base
394-
.get_mut(*constituent_index as u32)
395-
.target_base = 0_i64;
396-
continue;
397-
}
398-
399-
derivative_weights_sum += constituent.derivative_weight;
400-
401-
let target_weight = target_parent_weight
402-
.safe_mul(constituent.derivative_weight as i64)?
403-
.safe_div(PERCENTAGE_PRECISION_I64)?;
404-
405-
msg!(
406-
"constituent: {}, target weight: {}",
407-
constituent_index,
408-
target_weight,
409-
);
410-
let target_base = lp_pool
411-
.last_aum
412-
.cast::<i128>()?
413-
.safe_mul(target_weight as i128)?
414-
.safe_div(PERCENTAGE_PRECISION_I128)?
415-
.safe_mul(10_i128.pow(constituent.decimals as u32))?
416-
.safe_div(constituent.last_oracle_price as i128)?;
417-
418-
msg!(
419-
"constituent: {}, target base: {}",
420-
constituent_index,
421-
target_base
422-
);
423-
constituent_target_base
424-
.get_mut(*constituent_index as u32)
425-
.target_base = target_base.cast::<i64>()?;
426-
}
427-
constituent_target_base
428-
.get_mut(*parent_index as u32)
429-
.target_base = parent_target_base
430-
.safe_mul(PERCENTAGE_PRECISION_U64.safe_sub(derivative_weights_sum)? as i64)?
431-
.safe_div(PERCENTAGE_PRECISION_I64)?;
432-
}
277+
update_constituent_target_base_for_derivatives(
278+
aum,
279+
&derivative_groups,
280+
&constituent_map,
281+
&spot_market_map,
282+
&mut constituent_target_base,
283+
)?;
433284

434285
Ok(())
435286
}

programs/drift/src/instructions/user.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ use crate::state::fulfillment_params::openbook_v2::OpenbookV2FulfillmentParams;
6868
use crate::state::fulfillment_params::phoenix::PhoenixFulfillmentParams;
6969
use crate::state::fulfillment_params::serum::SerumFulfillmentParams;
7070
use crate::state::high_leverage_mode_config::HighLeverageModeConfig;
71-
use crate::state::lp_pool::{Constituent, LPPool};
7271
use crate::state::margin_calculation::MarginContext;
7372
use crate::state::oracle::StrictOraclePrice;
7473
use crate::state::order_params::{

programs/drift/src/state/constituent_map.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,46 @@ impl<'a> ConstituentMap<'a> {
198198
Ok(constituent_map)
199199
}
200200

201+
pub fn load_multiple<'c: 'a>(
202+
account_info: Vec<&'c AccountInfo<'a>>,
203+
must_be_writable: bool,
204+
) -> DriftResult<ConstituentMap<'a>> {
205+
let mut constituent_map: ConstituentMap = ConstituentMap(BTreeMap::new());
206+
207+
let account_info_iter = account_info.into_iter();
208+
for account_info in account_info_iter {
209+
let constituent_discriminator: [u8; 8] = Constituent::discriminator();
210+
let data = account_info
211+
.try_borrow_data()
212+
.or(Err(ErrorCode::ConstituentCouldNotLoad))?;
213+
214+
let expected_data_len = Constituent::SIZE;
215+
if data.len() < expected_data_len {
216+
return Err(ErrorCode::ConstituentCouldNotLoad);
217+
}
218+
219+
let account_discriminator = array_ref![data, 0, 8];
220+
if account_discriminator != &constituent_discriminator {
221+
return Err(ErrorCode::ConstituentCouldNotLoad);
222+
}
223+
224+
// constituent index 284 bytes from front of account
225+
let constituent_index = u16::from_le_bytes(*array_ref![data, 284, 2]);
226+
227+
let is_writable = account_info.is_writable;
228+
let account_loader: AccountLoader<Constituent> = AccountLoader::try_from(account_info)
229+
.or(Err(ErrorCode::ConstituentCouldNotLoad))?;
230+
231+
if must_be_writable && !is_writable {
232+
return Err(ErrorCode::ConstituentWrongMutability);
233+
}
234+
235+
constituent_map.0.insert(constituent_index, account_loader);
236+
}
237+
238+
Ok(constituent_map)
239+
}
240+
201241
pub fn empty() -> Self {
202242
ConstituentMap(BTreeMap::new())
203243
}

0 commit comments

Comments
 (0)