Skip to content

Commit a43b1a2

Browse files
committed
add lp_swap ix
1 parent bddb41f commit a43b1a2

File tree

11 files changed

+713
-55
lines changed

11 files changed

+713
-55
lines changed

programs/drift/src/instructions/admin.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use std::mem::size_of;
44
use crate::msg;
55
use crate::state::lp_pool::{
66
AmmConstituentDatum, AmmConstituentMapping, Constituent, ConstituentTargetWeights, LPPool,
7-
WeightDatum, AMM_MAP_PDA_SEED, CONSITUENT_PDA_SEED, CONSTITUENT_TARGET_WEIGHT_PDA_SEED,
7+
WeightDatum, AMM_MAP_PDA_SEED, CONSTITUENT_PDA_SEED, CONSTITUENT_TARGET_WEIGHT_PDA_SEED,
8+
CONSTITUENT_VAULT_PDA_SEED,
89
};
910
use anchor_lang::prelude::*;
1011
use anchor_spl::token::Token;
@@ -5314,7 +5315,12 @@ pub struct InitializeLpPool<'info> {
53145315
spot_market_index: u16,
53155316
)]
53165317
pub struct InitializeConstituent<'info> {
5317-
#[account(mut)]
5318+
#[account()]
5319+
pub state: Box<Account<'info, State>>,
5320+
#[account(
5321+
mut,
5322+
// constraint = admin.key() == admin_hot_wallet::id() || admin.key() == state.admin
5323+
)]
53185324
pub admin: Signer<'info>,
53195325

53205326
#[account(
@@ -5335,14 +5341,30 @@ pub struct InitializeConstituent<'info> {
53355341

53365342
#[account(
53375343
init,
5338-
seeds = [CONSITUENT_PDA_SEED.as_ref(), lp_pool.key().as_ref(), spot_market_index.to_le_bytes().as_ref()],
5344+
seeds = [CONSTITUENT_PDA_SEED.as_ref(), lp_pool.key().as_ref(), spot_market_index.to_le_bytes().as_ref()],
53395345
bump,
53405346
space = Constituent::SIZE,
53415347
payer = admin,
53425348
)]
53435349
pub constituent: AccountLoader<'info, Constituent>,
5350+
pub spot_market_mint: Box<InterfaceAccount<'info, Mint>>,
5351+
#[account(
5352+
init,
5353+
seeds = [CONSTITUENT_VAULT_PDA_SEED.as_ref(), lp_pool.key().as_ref(), spot_market_index.to_le_bytes().as_ref()],
5354+
bump,
5355+
payer = admin,
5356+
token::mint = spot_market_mint,
5357+
token::authority = drift_signer
5358+
)]
5359+
pub constituent_vault: Box<InterfaceAccount<'info, TokenAccount>>,
5360+
#[account(
5361+
constraint = state.signer.eq(&drift_signer.key())
5362+
)]
5363+
/// CHECK: program signer
5364+
pub drift_signer: AccountInfo<'info>,
53445365
pub rent: Sysvar<'info, Rent>,
53455366
pub system_program: Program<'info, System>,
5367+
pub token_program: Interface<'info, TokenInterface>,
53465368
}
53475369

53485370
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]

programs/drift/src/instructions/lp_pool.rs

Lines changed: 224 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,47 @@
1-
use anchor_lang::{prelude::*, Accounts, Key, Result, ToAccountInfo};
2-
3-
use crate::{
4-
error::ErrorCode,
5-
math::oracle::{is_oracle_valid_for_action, DriftAction},
6-
msg,
7-
state::{
8-
lp_pool::{AmmConstituentDatum, AmmConstituentMappingFixed, LPPool, WeightValidationFlags},
9-
perp_market_map::MarketSet,
10-
state::State,
11-
user::MarketType,
12-
zero_copy::{AccountZeroCopy, ZeroCopyLoader},
1+
use anchor_lang::{prelude::*, Accounts, Key, Result};
2+
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
3+
4+
use crate::error::ErrorCode;
5+
use crate::math::{
6+
oracle::{is_oracle_valid_for_action, DriftAction},
7+
safe_math::SafeMath,
8+
};
9+
use crate::msg;
10+
use crate::state::spot_market::SpotBalanceType;
11+
use crate::state::{
12+
lp_pool::{
13+
AmmConstituentDatum, AmmConstituentMappingFixed, Constituent, LPPool, WeightValidationFlags,
1314
},
14-
validate,
15+
perp_market_map::MarketSet,
16+
state::State,
17+
user::MarketType,
18+
zero_copy::{AccountZeroCopy, ZeroCopyLoader},
1519
};
20+
use crate::validate;
21+
1622
use solana_program::sysvar::clock::Clock;
1723

1824
use super::optional_accounts::{load_maps, AccountMaps};
25+
use crate::controller::spot_balance::update_spot_market_cumulative_interest;
26+
use crate::controller::token::{receive, send_from_program_vault};
27+
use crate::instructions::constraints::*;
1928
use crate::state::lp_pool::{AMM_MAP_PDA_SEED, CONSTITUENT_TARGET_WEIGHT_PDA_SEED};
2029

2130
pub fn handle_update_constituent_target_weights<'c: 'info, 'info>(
2231
ctx: Context<'_, '_, 'c, 'info, UpdateConstituentTargetWeights<'info>>,
2332
constituent_indexes: Vec<u16>,
2433
) -> Result<()> {
34+
msg!("HIHI!");
2535
let lp_pool = &ctx.accounts.lp_pool.load()?;
2636
let state = &ctx.accounts.state;
37+
msg!("datalen: {}", ctx.accounts.constituent_target_weights.data_len());
2738
let mut constituent_target_weights = ctx.accounts.constituent_target_weights.load_zc_mut()?;
2839

2940
let num_constituents = constituent_target_weights.len();
3041
let exists_invalid_constituent_index = constituent_indexes
3142
.iter()
3243
.any(|index| *index as u32 >= num_constituents);
44+
msg!("num_constituents: {} ", num_constituents);
3345

3446
validate!(
3547
!exists_invalid_constituent_index,
@@ -44,6 +56,7 @@ pub fn handle_update_constituent_target_weights<'c: 'info, 'info>(
4456
AmmConstituentDatum,
4557
AmmConstituentMappingFixed,
4658
> = ctx.accounts.amm_constituent_mapping.load_zc()?;
59+
msg!("amm constituent mapping weights: {} datalen: {}", amm_constituent_mapping.len(), ctx.accounts.amm_constituent_mapping.data_len());
4760

4861
let AccountMaps {
4962
perp_market_map,
@@ -100,6 +113,150 @@ pub fn handle_update_constituent_target_weights<'c: 'info, 'info>(
100113
Ok(())
101114
}
102115

116+
#[access_control(
117+
fill_not_paused(&ctx.accounts.state)
118+
)]
119+
pub fn handle_lp_pool_swap<'c: 'info, 'info>(
120+
ctx: Context<'_, '_, 'c, 'info, LPSwap<'info>>,
121+
in_market_index: u16,
122+
out_market_index: u16,
123+
in_amount: u64,
124+
min_out_amount: u64,
125+
) -> Result<()> {
126+
validate!(
127+
in_market_index != out_market_index,
128+
ErrorCode::InvalidSpotMarketAccount,
129+
"In and out spot market indices cannot be the same"
130+
)?;
131+
132+
let slot = Clock::get()?.slot;
133+
let now = Clock::get()?.unix_timestamp;
134+
let state = &ctx.accounts.state;
135+
let lp_pool = &ctx.accounts.lp_pool.load()?;
136+
137+
let mut in_constituent = ctx.accounts.in_constituent.load_mut()?;
138+
let mut out_constituent = ctx.accounts.out_constituent.load_mut()?;
139+
140+
let in_constituent_token_account = &ctx.accounts.constituent_in_token_account;
141+
let out_constituent_token_account = &ctx.accounts.constituent_out_token_account;
142+
143+
let constituent_target_weights = ctx.accounts.constituent_target_weights.load_zc()?;
144+
145+
let AccountMaps {
146+
perp_market_map,
147+
spot_market_map,
148+
mut oracle_map,
149+
} = load_maps(
150+
&mut ctx.remaining_accounts.iter().peekable(),
151+
&MarketSet::new(),
152+
&MarketSet::new(),
153+
slot,
154+
Some(state.oracle_guard_rails),
155+
)?;
156+
157+
let mut in_spot_market = spot_market_map.get_ref_mut(&in_market_index)?;
158+
let mut out_spot_market = spot_market_map.get_ref_mut(&out_market_index)?;
159+
160+
let in_oracle_id = in_spot_market.oracle_id();
161+
let out_oracle_id = out_spot_market.oracle_id();
162+
163+
let in_oracle = *oracle_map.get_price_data(&in_oracle_id)?;
164+
let out_oracle = *oracle_map.get_price_data(&out_oracle_id)?;
165+
166+
update_spot_market_cumulative_interest(&mut in_spot_market, Some(&in_oracle), now)?;
167+
168+
update_spot_market_cumulative_interest(&mut out_spot_market, Some(&out_oracle), now)?;
169+
170+
let in_constituent_balance =
171+
in_constituent.get_full_balance(in_constituent_token_account.amount)?;
172+
msg!(
173+
"in_constituent: {}, in_constituent_balance: {}",
174+
in_constituent.constituent_index,
175+
in_constituent_balance
176+
);
177+
let out_constituent_balance =
178+
out_constituent.get_full_balance(out_constituent_token_account.amount)?;
179+
msg!(
180+
"out_constituent: {}, out_constituent_balance: {}",
181+
out_constituent.constituent_index,
182+
out_constituent_balance
183+
);
184+
185+
let in_target_weight =
186+
constituent_target_weights.get_target_weight(in_constituent.constituent_index)?;
187+
let out_target_weight =
188+
constituent_target_weights.get_target_weight(out_constituent.constituent_index)?;
189+
190+
let (in_amount, out_amount, in_fee, out_fee) = lp_pool.get_swap_amount(
191+
&in_oracle,
192+
&out_oracle,
193+
&in_constituent,
194+
&out_constituent,
195+
&in_spot_market,
196+
&out_spot_market,
197+
in_constituent_token_account.amount,
198+
out_constituent_token_account.amount,
199+
in_target_weight,
200+
out_target_weight,
201+
in_amount,
202+
)?;
203+
let out_amount_net_fees = if out_fee > 0 {
204+
out_amount.safe_sub(out_fee.unsigned_abs() as u64)?
205+
} else {
206+
out_amount.safe_add(out_fee.unsigned_abs() as u64)?
207+
};
208+
209+
validate!(
210+
out_amount_net_fees >= min_out_amount,
211+
ErrorCode::SlippageOutsideLimit,
212+
format!(
213+
"Slippage outside limit: out_amount_net_fees({}) < min_out_amount({})",
214+
out_amount_net_fees, min_out_amount
215+
)
216+
.as_str()
217+
)?;
218+
219+
in_constituent.record_swap_fees(in_fee)?;
220+
out_constituent.record_swap_fees(out_fee)?;
221+
222+
// interactions: CPIs
223+
224+
let (transfer_from_vault, transfer_from_constituent) = out_constituent
225+
.get_amount_from_vaults_to_withdraw(
226+
out_constituent_token_account.amount,
227+
out_amount_net_fees,
228+
)?;
229+
230+
// transfer in from user token account to token vault
231+
receive(
232+
&ctx.accounts.token_program,
233+
&ctx.accounts.user_in_token_account,
234+
&ctx.accounts.constituent_in_token_account,
235+
&ctx.accounts.authority,
236+
in_amount,
237+
&Some((*ctx.accounts.in_market_mint).clone()),
238+
)?;
239+
ctx.accounts.constituent_in_token_account.reload()?;
240+
241+
// transfer out from token vault to constituent token account
242+
if transfer_from_vault > 0 {
243+
send_from_program_vault(
244+
&ctx.accounts.token_program,
245+
&ctx.accounts.out_spot_market_vault,
246+
&ctx.accounts.constituent_out_token_account,
247+
&ctx.accounts.drift_signer,
248+
state.signer_nonce,
249+
transfer_from_vault,
250+
&Some((*ctx.accounts.out_market_mint).clone()),
251+
)?;
252+
}
253+
ctx.accounts.constituent_out_token_account.reload()?;
254+
255+
// transfer out from constituent token account to user token account
256+
257+
Ok(())
258+
}
259+
103260
#[derive(Accounts)]
104261
#[instruction(
105262
lp_pool_name: [u8; 32],
@@ -127,3 +284,57 @@ pub struct UpdateConstituentTargetWeights<'info> {
127284
)]
128285
pub lp_pool: AccountLoader<'info, LPPool>,
129286
}
287+
288+
#[derive(Accounts)]
289+
pub struct LPSwap<'info> {
290+
/// CHECK: forced drift_signer
291+
pub drift_signer: AccountInfo<'info>,
292+
pub state: Box<Account<'info, State>>,
293+
pub lp_pool: AccountLoader<'info, LPPool>,
294+
#[account(
295+
mut,
296+
seeds = [CONSTITUENT_TARGET_WEIGHT_PDA_SEED.as_ref(), lp_pool.key().as_ref()],
297+
bump,
298+
)]
299+
/// CHECK: checked in ConstituentTargetWeightsZeroCopy checks
300+
pub constituent_target_weights: AccountInfo<'info>,
301+
302+
#[account(mut)]
303+
pub constituent_in_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
304+
#[account(mut)]
305+
pub constituent_out_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
306+
307+
#[account(mut)]
308+
pub user_in_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
309+
#[account(mut)]
310+
pub user_out_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
311+
312+
#[account(
313+
mut,
314+
constraint = &in_spot_market_vault.mint.eq(&in_market_mint.key())
315+
)]
316+
pub in_spot_market_vault: Box<InterfaceAccount<'info, TokenAccount>>,
317+
#[account(
318+
mut,
319+
constraint = &out_spot_market_vault.mint.eq(&out_market_mint.key())
320+
)]
321+
pub out_spot_market_vault: Box<InterfaceAccount<'info, TokenAccount>>,
322+
323+
#[account(mut)]
324+
pub in_constituent: AccountLoader<'info, Constituent>,
325+
#[account(mut)]
326+
pub out_constituent: AccountLoader<'info, Constituent>,
327+
328+
#[account(
329+
constraint = in_market_mint.key() == in_constituent.load()?.mint,
330+
)]
331+
pub in_market_mint: Box<InterfaceAccount<'info, Mint>>,
332+
#[account(
333+
constraint = out_market_mint.key() == out_constituent.load()?.mint,
334+
)]
335+
pub out_market_mint: Box<InterfaceAccount<'info, Mint>>,
336+
337+
pub authority: Signer<'info>,
338+
339+
pub token_program: Interface<'info, TokenInterface>,
340+
}

programs/drift/src/instructions/user.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ use crate::state::fulfillment_params::openbook_v2::OpenbookV2FulfillmentParams;
6767
use crate::state::fulfillment_params::phoenix::PhoenixFulfillmentParams;
6868
use crate::state::fulfillment_params::serum::SerumFulfillmentParams;
6969
use crate::state::high_leverage_mode_config::HighLeverageModeConfig;
70+
use crate::state::lp_pool::{Constituent, LPPool};
7071
use crate::state::margin_calculation::MarginContext;
7172
use crate::state::oracle::StrictOraclePrice;
7273
use crate::state::order_params::{

programs/drift/src/state/events.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,3 +672,34 @@ pub fn emit_buffers<T: AnchorSerialize + Discriminator>(
672672

673673
Ok(())
674674
}
675+
676+
#[event]
677+
#[derive(Default)]
678+
pub struct LPSwapRecord {
679+
pub ts: i64,
680+
pub authority: Pubkey,
681+
/// precision: out market mint precision, gross fees
682+
pub amount_out: u64,
683+
/// precision: in market mint precision, gross fees
684+
pub amount_in: u64,
685+
/// precision: fee on amount_out, in market mint precision
686+
pub fee_out: i64,
687+
/// precision: fee on amount_in, out market mint precision
688+
pub fee_in: i64,
689+
// out spot market index
690+
pub out_spot_market_index: u16,
691+
// in spot market index
692+
pub in_spot_market_index: u16,
693+
// out constituent index
694+
pub out_constituent_index: u16,
695+
// in constituent index
696+
pub in_constituent_index: u16,
697+
/// precision: PRICE_PRECISION
698+
pub out_oracle_price: i64,
699+
/// precision: PRICE_PRECISION
700+
pub in_oracle_price: i64,
701+
/// out token mint
702+
pub mint_out: u64,
703+
/// in token mint
704+
pub mint_in: u64,
705+
}

0 commit comments

Comments
 (0)