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
2 changes: 2 additions & 0 deletions programs/drift/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,8 @@ pub enum ErrorCode {
InvalidConstituent,
#[msg("Invalid Amm Constituent Mapping argument")]
InvalidAmmConstituentMappingArgument,
#[msg("Invalid update constituent update target weights argument")]
InvalidUpdateConstituentTargetWeightsArgument,
}

#[macro_export]
Expand Down
95 changes: 69 additions & 26 deletions programs/drift/src/instructions/lp_pool.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,50 @@
use anchor_lang::{
prelude::{Account, AccountInfo, AccountLoader, Context, Pubkey, Signer, SolanaSysvar},
Accounts, Result,
};
use anchor_lang::{prelude::*, Accounts, Key, Result, ToAccountInfo};

use crate::state::{
lp_pool::{
AmmConstituentDatum, AmmConstituentMappingFixed, ConstituentTargetWeightsFixed, LPPool,
WeightDatum, WeightValidationFlags,
use crate::{
error::ErrorCode,
math::oracle::{is_oracle_valid_for_action, DriftAction},
msg,
state::{
lp_pool::{AmmConstituentDatum, AmmConstituentMappingFixed, LPPool, WeightValidationFlags},
perp_market_map::MarketSet,
state::State,
user::MarketType,
zero_copy::{AccountZeroCopy, ZeroCopyLoader},
},
perp_market_map::MarketSet,
state::State,
zero_copy::{AccountZeroCopy, AccountZeroCopyMut, ZeroCopyLoader},
validate,
};
use solana_program::sysvar::clock::Clock;

use super::optional_accounts::{load_maps, AccountMaps};
use crate::state::lp_pool::{AMM_MAP_PDA_SEED, CONSTITUENT_TARGET_WEIGHT_PDA_SEED};

pub fn handle_update_dlp_target_weights<'info, 'c: 'info>(
ctx: Context<'_, 'info, 'c, 'info, UpdateDlpTargetWeights<'info>>,
pub fn handle_update_constituent_target_weights<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, UpdateConstituentTargetWeights<'info>>,
constituent_indexes: Vec<u16>,
) -> Result<()> {
let state = &ctx.accounts.state;
let lp_pool = &ctx.accounts.lp_pool.load()?;
let state = &ctx.accounts.state;
let mut constituent_target_weights = ctx.accounts.constituent_target_weights.load_zc_mut()?;

let num_constituents = constituent_target_weights.len();
let exists_invalid_constituent_index = constituent_indexes
.iter()
.any(|index| *index as u32 >= num_constituents);

validate!(
!exists_invalid_constituent_index,
ErrorCode::InvalidUpdateConstituentTargetWeightsArgument,
"Constituent index larger than number of constituent target weights"
)?;

let slot = Clock::get()?.slot;

let amm_constituent_mapping: AccountZeroCopy<
'info,
'_,
AmmConstituentDatum,
AmmConstituentMappingFixed,
> = ctx.accounts.amm_constituent_mapping.load_zc()?;

let mut target_weights: AccountZeroCopyMut<'info, WeightDatum, ConstituentTargetWeightsFixed> =
ctx.accounts.constituent_target_weights.load_zc_mut()?;

let AccountMaps {
perp_market_map,
spot_market_map,
Expand All @@ -49,18 +61,33 @@ pub fn handle_update_dlp_target_weights<'info, 'c: 'info>(
let mut oracle_prices: Vec<i64> = vec![];
for (_, datum) in amm_constituent_mapping.iter().enumerate() {
let perp_market = perp_market_map.get_ref(&datum.perp_market_index)?;
let amm_inventory = perp_market.amm.get_protocol_owned_position()?;
amm_inventories.push((datum.perp_market_index, amm_inventory));

let oracle_data = oracle_map.get_price_data_and_guard_rails(&(
perp_market.amm.oracle,
perp_market.amm.oracle_source,
))?;
let oracle_data = oracle_map.get_price_data_and_validity(
MarketType::Perp,
datum.perp_market_index,
&perp_market.oracle_id(),
perp_market
.amm
.historical_oracle_data
.last_oracle_price_twap,
perp_market.get_max_confidence_interval_multiplier()?,
)?;

if !is_oracle_valid_for_action(
oracle_data.1,
Some(DriftAction::UpdateDlpConstituentTargetWeights),
)? {
msg!("Oracle data for perp market {} and constituent index {} is invalid. Skipping update",
datum.perp_market_index, datum.constituent_index);
continue;
}

let amm_inventory = perp_market.amm.get_protocol_owned_position()?;
amm_inventories.push((datum.perp_market_index, amm_inventory));
oracle_prices.push(oracle_data.0.price);
}

target_weights.update_target_weights(
constituent_target_weights.update_target_weights(
&amm_constituent_mapping,
amm_inventories.as_slice(),
constituent_indexes.as_slice(),
Expand All @@ -74,13 +101,29 @@ pub fn handle_update_dlp_target_weights<'info, 'c: 'info>(
}

#[derive(Accounts)]
pub struct UpdateDlpTargetWeights<'info> {
#[instruction(
lp_pool_name: [u8; 32],
)]
pub struct UpdateConstituentTargetWeights<'info> {
pub state: Box<Account<'info, State>>,
#[account(mut)]
pub keeper: Signer<'info>,
#[account(
seeds = [AMM_MAP_PDA_SEED.as_ref(), lp_pool.key().as_ref()],
bump,
)]
/// CHECK: checked in AmmConstituentMappingZeroCopy checks
pub amm_constituent_mapping: AccountInfo<'info>,
#[account(
mut,
seeds = [CONSTITUENT_TARGET_WEIGHT_PDA_SEED.as_ref(), lp_pool.key().as_ref()],
bump,
)]
/// CHECK: checked in ConstituentTargetWeightsZeroCopy checks
pub constituent_target_weights: AccountInfo<'info>,
#[account(
seeds = [b"lp_pool", lp_pool_name.as_ref()],
bump,
)]
pub lp_pool: AccountLoader<'info, LPPool>,
}
8 changes: 8 additions & 0 deletions programs/drift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,14 @@ pub mod drift {
) -> Result<()> {
handle_add_amm_constituent_data(ctx, init_amm_constituent_mapping_data)
}

pub fn update_dlp_constituent_target_weights<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, UpdateConstituentTargetWeights<'info>>,
lp_pool_name: [u8; 32],
constituent_indexes: Vec<u16>,
) -> Result<()> {
handle_update_constituent_target_weights(ctx, constituent_indexes)
}
}

#[cfg(not(feature = "no-entrypoint"))]
Expand Down
4 changes: 4 additions & 0 deletions programs/drift/src/math/oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub enum DriftAction {
UpdateTwap,
UpdateAMMCurve,
OracleOrderPrice,
UpdateDlpConstituentTargetWeights,
}

pub fn is_oracle_valid_for_action(
Expand Down Expand Up @@ -128,6 +129,9 @@ pub fn is_oracle_valid_for_action(
),
DriftAction::UpdateTwap => !matches!(oracle_validity, OracleValidity::NonPositive),
DriftAction::UpdateAMMCurve => !matches!(oracle_validity, OracleValidity::NonPositive),
DriftAction::UpdateDlpConstituentTargetWeights => {
!matches!(oracle_validity, OracleValidity::NonPositive)
}
},
None => {
matches!(oracle_validity, OracleValidity::Valid)
Expand Down
4 changes: 4 additions & 0 deletions programs/drift/src/state/lp_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,10 @@ impl<'a> AccountZeroCopyMut<'a, WeightDatum, ConstituentTargetWeightsFixed> {
return Err(ErrorCode::DefaultError);
}

total_weight = total_weight
.saturating_add(target_weight)
.saturating_add(PERCENTAGE_PRECISION_I64 as i128);
Copy link
Member

Choose a reason for hiding this comment

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

should remove this line, right?


let cell = self.get_mut(i as u32);
cell.weight = target_weight as i64;
cell.last_slot = slot;
Expand Down
22 changes: 9 additions & 13 deletions programs/drift/src/state/zero_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ pub trait ZeroCopyLoader<'a, T, F> {
fn load_zc_mut(&'a self) -> DriftResult<AccountZeroCopyMut<'a, T, F>>;
}

pub fn load_generic<'a, F, T>(
acct: &'a AccountInfo<'a>,
pub fn load_generic<'a, 'info, F, T>(
acct: &'a AccountInfo<'info>,
expected_disc: [u8; 8],
program_id: Pubkey,
) -> DriftResult<AccountZeroCopy<'a, T, F>>
Expand Down Expand Up @@ -111,8 +111,8 @@ where
})
}

pub fn load_generic_mut<'a, F, T>(
acct: &'a AccountInfo<'a>,
pub fn load_generic_mut<'a, 'info, F, T>(
acct: &'a AccountInfo<'info>,
expected_disc: [u8; 8],
program_id: Pubkey,
) -> DriftResult<AccountZeroCopyMut<'a, T, F>>
Expand Down Expand Up @@ -145,17 +145,13 @@ where
})
}

/// Anything that you can pull a zero‑copy view of `Elem`+`Fixed` out of.
pub trait ToZeroCopy<'info, Elem, Fixed> {
fn to_zc(&self) -> DriftResult<AccountZeroCopy<'info, Elem, Fixed>>;
fn to_zc_mut(&self) -> DriftResult<AccountZeroCopyMut<'info, Elem, Fixed>>;
}

#[macro_export]
macro_rules! impl_zero_copy_loader {
($Acc:ty, $ID:path, $Fixed:ty, $Elem:ty) => {
impl<'a> crate::state::zero_copy::ZeroCopyLoader<'a, $Elem, $Fixed> for AccountInfo<'a> {
fn load_zc(
impl<'info> crate::state::zero_copy::ZeroCopyLoader<'_, $Elem, $Fixed>
for AccountInfo<'info>
{
fn load_zc<'a>(
self: &'a Self,
) -> crate::error::DriftResult<
crate::state::zero_copy::AccountZeroCopy<'a, $Elem, $Fixed>,
Expand All @@ -167,7 +163,7 @@ macro_rules! impl_zero_copy_loader {
)
}

fn load_zc_mut(
fn load_zc_mut<'a>(
self: &'a Self,
) -> crate::error::DriftResult<
crate::state::zero_copy::AccountZeroCopyMut<'a, $Elem, $Fixed>,
Expand Down
72 changes: 72 additions & 0 deletions sdk/src/driftClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import {
UserStatsAccount,
ProtectedMakerModeConfig,
SignedMsgOrderParamsDelegateMessage,
AmmConstituentMapping,
AmmConstituentDatum,
} from './types';
import driftIDL from './idl/drift.json';

Expand Down Expand Up @@ -104,6 +106,9 @@ import {
getUserAccountPublicKeySync,
getUserStatsAccountPublicKey,
getSignedMsgWsDelegatesAccountPublicKey,
getConstituentTargetWeightsPublicKey,
getAmmConstituentMappingPublicKey,
getLpPoolPublicKey,
} from './addresses/pda';
import {
DataAndSlot,
Expand Down Expand Up @@ -9678,6 +9683,73 @@ export class DriftClient {
return txSig;
}

public async updateDlpConstituentTargetWeights(
lpPoolName: number[],
constituentIndexesToUpdate: number[],
ammConstituentMapping: AmmConstituentMapping,
txParams?: TxParams
): Promise<TransactionSignature> {
const { txSig } = await this.sendTransaction(
await this.buildTransaction(
await this.getUpdateDlpConstituentTargetWeightsIx(
lpPoolName,
constituentIndexesToUpdate,
ammConstituentMapping
),
txParams
),
[],
this.opts
);
return txSig;
}

public async getUpdateDlpConstituentTargetWeightsIx(
lpPoolName: number[],
constituentIndexesToUpdate: number[],
ammConstituentMapping: AmmConstituentMapping
): Promise<TransactionInstruction> {
const lpPool = getLpPoolPublicKey(this.program.programId, lpPoolName);
const ammConstituentMappingPublicKey = getAmmConstituentMappingPublicKey(
this.program.programId,
lpPool
);
const constituentTargetWeights = getConstituentTargetWeightsPublicKey(
this.program.programId,
lpPool
);

const perpMarketIndexes = ammConstituentMapping.weights
.filter((datum: AmmConstituentDatum) => {
return constituentIndexesToUpdate.includes(datum.constituentIndex);
})
.map((datum: AmmConstituentDatum) => datum.perpMarketIndex);

const remainingAccounts = this.getRemainingAccounts({
readablePerpMarketIndex: perpMarketIndexes,
userAccounts: [],
});

return this.program.instruction.updateDlpConstituentTargetWeights(
lpPoolName,
constituentIndexesToUpdate,
{
accounts: {
keeper: this.wallet.publicKey,
lpPool,
ammConstituentMapping: ammConstituentMappingPublicKey,
constituentTargetWeights,
state: await this.getStatePublicKey(),
},
remainingAccounts,
}
);
}

/**
* Below here are the transaction sending functions
*/

private handleSignedTransaction(signedTxs: SignedTxData[]) {
if (this.enableMetricsEvents && this.metricsEventEmitter) {
this.metricsEventEmitter.emit('txSigned', signedTxs);
Expand Down
Loading