11use anchor_lang:: { prelude:: * , Accounts , Key , Result } ;
2+ use anchor_spl:: token_interface:: { Mint , TokenAccount , TokenInterface } ;
23
34use crate :: {
45 error:: ErrorCode ,
@@ -12,22 +13,30 @@ use crate::{
1213 state:: {
1314 constituent_map:: { ConstituentMap , ConstituentSet } ,
1415 lp_pool:: {
15- AmmConstituentDatum , AmmConstituentMappingFixed , LPPool , WeightValidationFlags ,
16- CONSTITUENT_PDA_SEED ,
16+ AmmConstituentDatum , AmmConstituentMappingFixed , Constituent , LPPool ,
17+ WeightValidationFlags ,
1718 } ,
1819 oracle:: OraclePriceData ,
1920 perp_market:: { AmmCacheFixed , CacheInfo , AMM_POSITIONS_CACHE } ,
2021 perp_market_map:: MarketSet ,
22+ spot_market_map:: get_writable_spot_market_set_from_many,
2123 state:: State ,
2224 user:: MarketType ,
2325 zero_copy:: { AccountZeroCopy , ZeroCopyLoader } ,
26+ events:: LPSwapRecord ,
2427 } ,
2528 validate,
2629} ;
30+
2731use solana_program:: sysvar:: clock:: Clock ;
2832
2933use super :: optional_accounts:: { load_maps, AccountMaps } ;
30- use crate :: state:: lp_pool:: { AMM_MAP_PDA_SEED , CONSTITUENT_TARGET_WEIGHT_PDA_SEED } ;
34+ use crate :: controller:: spot_balance:: update_spot_market_cumulative_interest;
35+ use crate :: controller:: token:: { receive, send_from_program_vault} ;
36+ use crate :: instructions:: constraints:: * ;
37+ use crate :: state:: lp_pool:: {
38+ AMM_MAP_PDA_SEED , CONSTITUENT_PDA_SEED , CONSTITUENT_TARGET_WEIGHT_PDA_SEED ,
39+ } ;
3140
3241pub fn handle_update_constituent_target_weights < ' c : ' info , ' info > (
3342 ctx : Context < ' _ , ' _ , ' c , ' info , UpdateConstituentTargetWeights < ' info > > ,
@@ -191,7 +200,6 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
191200 } ;
192201
193202 if oracle_price. is_none ( ) {
194- msg ! ( "hi" ) ;
195203 return Err ( ErrorCode :: OracleTooStaleForLPAUMUpdate . into ( ) ) ;
196204 }
197205
@@ -226,16 +234,214 @@ pub fn handle_update_lp_pool_aum<'c: 'info, 'info>(
226234 Ok ( ( ) )
227235}
228236
237+ #[ access_control(
238+ fill_not_paused( & ctx. accounts. state)
239+ ) ]
240+ pub fn handle_lp_pool_swap < ' c : ' info , ' info > (
241+ ctx : Context < ' _ , ' _ , ' c , ' info , LPPoolSwap < ' info > > ,
242+ in_market_index : u16 ,
243+ out_market_index : u16 ,
244+ in_amount : u64 ,
245+ min_out_amount : u64 ,
246+ ) -> Result < ( ) > {
247+ validate ! (
248+ in_market_index != out_market_index,
249+ ErrorCode :: InvalidSpotMarketAccount ,
250+ "In and out spot market indices cannot be the same"
251+ ) ?;
252+
253+ let slot = Clock :: get ( ) ?. slot ;
254+ let now = Clock :: get ( ) ?. unix_timestamp ;
255+ let state = & ctx. accounts . state ;
256+ let lp_pool = & ctx. accounts . lp_pool . load ( ) ?;
257+
258+ let mut in_constituent = ctx. accounts . in_constituent . load_mut ( ) ?;
259+ let mut out_constituent = ctx. accounts . out_constituent . load_mut ( ) ?;
260+
261+ let constituent_target_weights = ctx. accounts . constituent_target_weights . load_zc ( ) ?;
262+
263+ let AccountMaps {
264+ perp_market_map : _,
265+ spot_market_map,
266+ mut oracle_map,
267+ } = load_maps (
268+ & mut ctx. remaining_accounts . iter ( ) . peekable ( ) ,
269+ & MarketSet :: new ( ) ,
270+ & get_writable_spot_market_set_from_many ( vec ! [ in_market_index, out_market_index] ) ,
271+ slot,
272+ Some ( state. oracle_guard_rails ) ,
273+ ) ?;
274+
275+ let mut in_spot_market = spot_market_map. get_ref_mut ( & in_market_index) ?;
276+ let mut out_spot_market = spot_market_map. get_ref_mut ( & out_market_index) ?;
277+
278+ let in_oracle_id = in_spot_market. oracle_id ( ) ;
279+ let out_oracle_id = out_spot_market. oracle_id ( ) ;
280+
281+ let ( in_oracle, in_oracle_validity) = oracle_map. get_price_data_and_validity (
282+ MarketType :: Spot ,
283+ in_spot_market. market_index ,
284+ & in_oracle_id,
285+ in_spot_market. historical_oracle_data . last_oracle_price_twap ,
286+ in_spot_market. get_max_confidence_interval_multiplier ( ) ?,
287+ ) ?;
288+ let in_oracle = in_oracle. clone ( ) ;
289+
290+ let ( out_oracle, out_oracle_validity) = oracle_map. get_price_data_and_validity (
291+ MarketType :: Spot ,
292+ out_spot_market. market_index ,
293+ & out_oracle_id,
294+ out_spot_market
295+ . historical_oracle_data
296+ . last_oracle_price_twap ,
297+ out_spot_market. get_max_confidence_interval_multiplier ( ) ?,
298+ ) ?;
299+
300+ if !is_oracle_valid_for_action ( in_oracle_validity, Some ( DriftAction :: LpPoolSwap ) ) ? {
301+ msg ! (
302+ "In oracle data for spot market {} is invalid for lp pool swap." ,
303+ in_spot_market. market_index,
304+ ) ;
305+ return Err ( ErrorCode :: InvalidOracle . into ( ) ) ;
306+ }
307+
308+ if !is_oracle_valid_for_action ( out_oracle_validity, Some ( DriftAction :: LpPoolSwap ) ) ? {
309+ msg ! (
310+ "Out oracle data for spot market {} is invalid for lp pool swap." ,
311+ out_spot_market. market_index,
312+ ) ;
313+ return Err ( ErrorCode :: InvalidOracle . into ( ) ) ;
314+ }
315+
316+ update_spot_market_cumulative_interest ( & mut in_spot_market, Some ( & in_oracle) , now) ?;
317+ update_spot_market_cumulative_interest ( & mut out_spot_market, Some ( & out_oracle) , now) ?;
318+
319+ let in_target_weight =
320+ constituent_target_weights. get_target_weight ( in_constituent. constituent_index ) ?;
321+ let out_target_weight =
322+ constituent_target_weights. get_target_weight ( out_constituent. constituent_index ) ?;
323+
324+ let ( in_amount, out_amount, in_fee, out_fee) = lp_pool. get_swap_amount (
325+ & in_oracle,
326+ & out_oracle,
327+ & in_constituent,
328+ & out_constituent,
329+ & in_spot_market,
330+ & out_spot_market,
331+ in_target_weight,
332+ out_target_weight,
333+ in_amount,
334+ ) ?;
335+ msg ! (
336+ "in_amount: {}, out_amount: {}, in_fee: {}, out_fee: {}" ,
337+ in_amount,
338+ out_amount,
339+ in_fee,
340+ out_fee
341+ ) ;
342+ let out_amount_net_fees = if out_fee > 0 {
343+ out_amount. safe_sub ( out_fee. unsigned_abs ( ) as u64 ) ?
344+ } else {
345+ out_amount. safe_add ( out_fee. unsigned_abs ( ) as u64 ) ?
346+ } ;
347+
348+ validate ! (
349+ out_amount_net_fees >= min_out_amount,
350+ ErrorCode :: SlippageOutsideLimit ,
351+ format!(
352+ "Slippage outside limit: out_amount_net_fees({}) < min_out_amount({})" ,
353+ out_amount_net_fees, min_out_amount
354+ )
355+ . as_str( )
356+ ) ?;
357+
358+ validate ! (
359+ out_amount_net_fees <= out_constituent. token_balance,
360+ ErrorCode :: InsufficientConstituentTokenBalance ,
361+ format!(
362+ "Insufficient out constituent balance: out_amount_net_fees({}) > out_constituent.token_balance({})" ,
363+ out_amount_net_fees, out_constituent. token_balance
364+ )
365+ . as_str( )
366+ ) ?;
367+
368+ in_constituent. record_swap_fees ( in_fee) ?;
369+ out_constituent. record_swap_fees ( out_fee) ?;
370+
371+ emit ! ( LPSwapRecord {
372+ ts: now,
373+ authority: ctx. accounts. authority. key( ) ,
374+ amount_out: out_amount_net_fees,
375+ amount_in: in_amount,
376+ fee_out: out_fee,
377+ fee_in: in_fee,
378+ out_spot_market_index: out_market_index,
379+ in_spot_market_index: in_market_index,
380+ out_constituent_index: out_constituent. constituent_index,
381+ in_constituent_index: in_constituent. constituent_index,
382+ out_oracle_price: out_oracle. price,
383+ in_oracle_price: in_oracle. price,
384+ mint_out: out_constituent. mint,
385+ mint_in: in_constituent. mint,
386+ } ) ;
387+
388+ receive (
389+ & ctx. accounts . token_program ,
390+ & ctx. accounts . user_in_token_account ,
391+ & ctx. accounts . constituent_in_token_account ,
392+ & ctx. accounts . authority ,
393+ in_amount,
394+ & Some ( ( * ctx. accounts . in_market_mint ) . clone ( ) ) ,
395+ ) ?;
396+
397+ send_from_program_vault (
398+ & ctx. accounts . token_program ,
399+ & ctx. accounts . constituent_out_token_account ,
400+ & ctx. accounts . user_out_token_account ,
401+ & ctx. accounts . drift_signer ,
402+ state. signer_nonce ,
403+ out_amount_net_fees,
404+ & Some ( ( * ctx. accounts . out_market_mint ) . clone ( ) ) ,
405+ ) ?;
406+
407+ ctx. accounts . constituent_in_token_account . reload ( ) ?;
408+ ctx. accounts . constituent_out_token_account . reload ( ) ?;
409+
410+ in_constituent. sync_token_balance ( ctx. accounts . constituent_in_token_account . amount ) ;
411+ out_constituent. sync_token_balance ( ctx. accounts . constituent_out_token_account . amount ) ;
412+
413+ Ok ( ( ) )
414+ }
415+
229416#[ derive( Accounts ) ]
230417#[ instruction(
231418 lp_pool_name: [ u8 ; 32 ] ,
232419) ]
233- pub struct UpdateLPPoolAum < ' info > {
420+ pub struct UpdateConstituentTargetWeights < ' info > {
234421 pub state : Box < Account < ' info , State > > ,
235422 #[ account( mut ) ]
236423 pub keeper : Signer < ' info > ,
424+ #[ account(
425+ seeds = [ AMM_MAP_PDA_SEED . as_ref( ) , lp_pool. key( ) . as_ref( ) ] ,
426+ bump,
427+ ) ]
428+ /// CHECK: checked in AmmConstituentMappingZeroCopy checks
429+ pub amm_constituent_mapping : AccountInfo < ' info > ,
430+ #[ account(
431+ mut ,
432+ seeds = [ CONSTITUENT_TARGET_WEIGHT_PDA_SEED . as_ref( ) , lp_pool. key( ) . as_ref( ) ] ,
433+ bump,
434+ ) ]
435+ /// CHECK: checked in ConstituentTargetWeightsZeroCopy checks
436+ pub constituent_target_weights : AccountInfo < ' info > ,
237437 #[ account(
238438 mut ,
439+ seeds = [ AMM_POSITIONS_CACHE . as_ref( ) ] ,
440+ bump,
441+ ) ]
442+ /// CHECK: checked in ConstituentTargetWeightsZeroCopy checks
443+ pub amm_cache : AccountInfo < ' info > ,
444+ #[ account(
239445 seeds = [ b"lp_pool" , lp_pool_name. as_ref( ) ] ,
240446 bump,
241447 ) ]
@@ -246,33 +452,80 @@ pub struct UpdateLPPoolAum<'info> {
246452#[ instruction(
247453 lp_pool_name: [ u8 ; 32 ] ,
248454) ]
249- pub struct UpdateConstituentTargetWeights < ' info > {
455+ pub struct UpdateLPPoolAum < ' info > {
250456 pub state : Box < Account < ' info , State > > ,
251457 #[ account( mut ) ]
252458 pub keeper : Signer < ' info > ,
253459 #[ account(
254- seeds = [ AMM_MAP_PDA_SEED . as_ref( ) , lp_pool. key( ) . as_ref( ) ] ,
460+ mut ,
461+ seeds = [ b"lp_pool" , lp_pool_name. as_ref( ) ] ,
255462 bump,
256463 ) ]
257- /// CHECK: checked in AmmConstituentMappingZeroCopy checks
258- pub amm_constituent_mapping : AccountInfo < ' info > ,
464+ pub lp_pool : AccountLoader < ' info , LPPool > ,
465+ }
466+
467+ /// `in`/`out` is in the program's POV for this swap. So `user_in_token_account` is the user owned token account
468+ /// for the `in` token for this swap.
469+ #[ derive( Accounts ) ]
470+ #[ instruction(
471+ in_market_index: u16 ,
472+ out_market_index: u16 ,
473+ ) ]
474+ pub struct LPPoolSwap < ' info > {
475+ /// CHECK: forced drift_signer
476+ pub drift_signer : AccountInfo < ' info > ,
477+ pub state : Box < Account < ' info , State > > ,
478+ pub lp_pool : AccountLoader < ' info , LPPool > ,
259479 #[ account(
260480 mut ,
261481 seeds = [ CONSTITUENT_TARGET_WEIGHT_PDA_SEED . as_ref( ) , lp_pool. key( ) . as_ref( ) ] ,
262482 bump,
263483 ) ]
264484 /// CHECK: checked in ConstituentTargetWeightsZeroCopy checks
265485 pub constituent_target_weights : AccountInfo < ' info > ,
486+
487+ #[ account( mut ) ]
488+ pub constituent_in_token_account : Box < InterfaceAccount < ' info , TokenAccount > > ,
489+ #[ account( mut ) ]
490+ pub constituent_out_token_account : Box < InterfaceAccount < ' info , TokenAccount > > ,
491+
266492 #[ account(
267493 mut ,
268- seeds = [ AMM_POSITIONS_CACHE . as_ref( ) ] ,
494+ constraint = user_in_token_account. mint. eq( & constituent_in_token_account. mint)
495+ ) ]
496+ pub user_in_token_account : Box < InterfaceAccount < ' info , TokenAccount > > ,
497+ #[ account(
498+ mut ,
499+ constraint = user_out_token_account. mint. eq( & constituent_out_token_account. mint)
500+ ) ]
501+ pub user_out_token_account : Box < InterfaceAccount < ' info , TokenAccount > > ,
502+
503+ #[ account(
504+ mut ,
505+ seeds = [ CONSTITUENT_PDA_SEED . as_ref( ) , lp_pool. key( ) . as_ref( ) , in_market_index. to_le_bytes( ) . as_ref( ) ] ,
269506 bump,
507+ constraint = in_constituent. load( ) ?. mint. eq( & constituent_in_token_account. mint)
270508 ) ]
271- /// CHECK: checked in ConstituentTargetWeightsZeroCopy checks
272- pub amm_cache : AccountInfo < ' info > ,
509+ pub in_constituent : AccountLoader < ' info , Constituent > ,
273510 #[ account(
274- seeds = [ b"lp_pool" , lp_pool_name. as_ref( ) ] ,
511+ mut ,
512+ seeds = [ CONSTITUENT_PDA_SEED . as_ref( ) , lp_pool. key( ) . as_ref( ) , out_market_index. to_le_bytes( ) . as_ref( ) ] ,
275513 bump,
514+ constraint = out_constituent. load( ) ?. mint. eq( & constituent_out_token_account. mint)
276515 ) ]
277- pub lp_pool : AccountLoader < ' info , LPPool > ,
516+ pub out_constituent : AccountLoader < ' info , Constituent > ,
517+
518+ #[ account(
519+ constraint = in_market_mint. key( ) == in_constituent. load( ) ?. mint,
520+ ) ]
521+ pub in_market_mint : Box < InterfaceAccount < ' info , Mint > > ,
522+ #[ account(
523+ constraint = out_market_mint. key( ) == out_constituent. load( ) ?. mint,
524+ ) ]
525+ pub out_market_mint : Box < InterfaceAccount < ' info , Mint > > ,
526+
527+ pub authority : Signer < ' info > ,
528+
529+ // TODO: in/out token program
530+ pub token_program : Interface < ' info , TokenInterface > ,
278531}
0 commit comments