diff --git a/CHANGELOG.md b/CHANGELOG.md index 741acba66..38691b1f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features -### Fixes +- program: add recenter amm ix ([#836](https://github.com/drift-labs/protocol-v2/pull/836)) -- program: enable jit maker to fill same slot as taker placed ([#835](https://github.com/drift-labs/protocol-v2/pull/835)) +### Fixes ### Breaking diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index 9b253ac2b..cb84f5b05 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -745,10 +745,63 @@ pub fn move_price( validate!( (quote_asset_reserve.cast::()? - amm.quote_asset_reserve.cast::()?).abs() < 100, ErrorCode::InvalidAmmDetected, + "quote_asset_reserve passed doesnt reconcile enough {} vs {}", + quote_asset_reserve.cast::()?, + amm.quote_asset_reserve.cast::()? + )?; + + amm.sqrt_k = sqrt_k; + + let (_, terminal_quote_reserves, terminal_base_reserves) = + amm::calculate_terminal_price_and_reserves(amm)?; + amm.terminal_quote_asset_reserve = terminal_quote_reserves; + + let (min_base_asset_reserve, max_base_asset_reserve) = + amm::calculate_bid_ask_bounds(amm.concentration_coef, terminal_base_reserves)?; + + amm.max_base_asset_reserve = max_base_asset_reserve; + amm.min_base_asset_reserve = min_base_asset_reserve; + + let reserve_price_after = amm.reserve_price()?; + update_spreads(amm, reserve_price_after)?; + + Ok(()) +} + +// recenter peg with balanced terminal reserves +pub fn recenter_perp_market_amm(amm: &mut AMM, peg_multiplier: u128, sqrt_k: u128) -> DriftResult { + // calculate base/quote reserves for balanced terminal reserves + let swap_direction = if amm.base_asset_amount_with_amm > 0 { + SwapDirection::Remove + } else { + SwapDirection::Add + }; + let (new_quote_asset_amount, new_base_asset_amount) = amm::calculate_swap_output( + amm.base_asset_amount_with_amm.unsigned_abs(), + sqrt_k, + swap_direction, + sqrt_k, + )?; + + amm.base_asset_reserve = new_base_asset_amount; + + let k = bn::U256::from(sqrt_k).safe_mul(bn::U256::from(sqrt_k))?; + + amm.quote_asset_reserve = k + .safe_div(bn::U256::from(new_base_asset_amount))? + .try_to_u128()?; + + validate!( + (new_quote_asset_amount.cast::()? - amm.quote_asset_reserve.cast::()?).abs() + < 100, + ErrorCode::InvalidAmmDetected, "quote_asset_reserve passed doesnt reconcile enough" )?; amm.sqrt_k = sqrt_k; + // todo: could calcualte terminal state cost for altering sqrt_k + + amm.peg_multiplier = peg_multiplier; let (_, terminal_quote_reserves, terminal_base_reserves) = amm::calculate_terminal_price_and_reserves(amm)?; diff --git a/programs/drift/src/controller/position/tests.rs b/programs/drift/src/controller/position/tests.rs index 77b3ce7c5..a74f79fec 100644 --- a/programs/drift/src/controller/position/tests.rs +++ b/programs/drift/src/controller/position/tests.rs @@ -1,3 +1,6 @@ +use crate::controller::amm::{ + calculate_base_swap_output_with_spread, move_price, recenter_perp_market_amm, swap_base_asset, +}; use crate::controller::position::{ update_lp_market_position, update_position_and_market, PositionDelta, }; @@ -6,15 +9,21 @@ use crate::controller::lp::{apply_lp_rebase_to_perp_market, settle_lp_position}; use crate::controller::repeg::_update_amm; use crate::math::constants::{ - AMM_RESERVE_PRECISION, AMM_RESERVE_PRECISION_I128, BASE_PRECISION_I64, PRICE_PRECISION_I64, - PRICE_PRECISION_U64, QUOTE_PRECISION_I128, + AMM_RESERVE_PRECISION, AMM_RESERVE_PRECISION_I128, BASE_PRECISION, BASE_PRECISION_I64, + PRICE_PRECISION_I64, PRICE_PRECISION_U64, QUOTE_PRECISION_I128, }; +use crate::math::position::swap_direction_to_close_position; use crate::state::oracle::OraclePriceData; use crate::state::oracle_map::OracleMap; use crate::state::perp_market::{AMMLiquiditySplit, PerpMarket, AMM}; +use crate::state::perp_market_map::PerpMarketMap; use crate::state::state::State; use crate::state::user::PerpPosition; -use crate::test_utils::create_account_info; +use crate::test_utils::{create_account_info, get_account_bytes}; + +use crate::bn::U192; +use crate::math::cp_curve::{adjust_k_cost, get_update_k_result, update_k}; +use crate::test_utils::get_hardcoded_pyth_price; use anchor_lang::prelude::AccountLoader; use solana_program::pubkey::Pubkey; use std::str::FromStr; @@ -1383,9 +1392,49 @@ fn update_amm_near_boundary2() { let mut lamports = 0; let perp_market_account_info = create_account_info(&key, true, &mut lamports, perp_market_bytes, &owner); + let market_map = PerpMarketMap::load_one(&perp_market_account_info, true).unwrap(); - let perp_market_loader: AccountLoader = - AccountLoader::try_from(&perp_market_account_info).unwrap(); + let oracle_market_str = String::from("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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + let mut decoded_bytes = base64::decode(oracle_market_str).unwrap(); + let oracle_market_bytes = decoded_bytes.as_mut_slice(); + + let key = Pubkey::from_str("8ihFLu5FimgTQ1Unh4dVyEHUGodJ5gJQCrQf4KUVB9bN").unwrap(); + let owner = Pubkey::from_str("FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH").unwrap(); + let mut lamports = 0; + let jto_market_account_info = + create_account_info(&key, true, &mut lamports, oracle_market_bytes, &owner); + + let slot = 234919073; + let now = 1702120657; + let mut oracle_map = OracleMap::load_one(&jto_market_account_info, slot, None).unwrap(); + + // let perp_market_old = market_map.get_ref(&4).unwrap(); + + let mut perp_market = market_map.get_ref_mut(&4).unwrap(); + + println!("perp_market: {:?}", perp_market.amm.last_update_slot); + + let oracle_price_data = oracle_map.get_price_data(&key).unwrap(); + + let state = State::default(); + + let cost = _update_amm(&mut perp_market, oracle_price_data, &state, now, slot).unwrap(); + + assert_eq!(cost, 2987010); +} + +#[test] +fn recenter_amm_1() { + let perp_market_str: String = String::from("Ct8MLGv1N/cU6tVVkVpIHdjrXil5+Blo7M7no01SEzFkvCN2nSnel3KwISF8o/5okioZqvmQEJy52E6a0AS00gJa1vUpMUQZIAjcAAAAAAAAAAAAAAAAAAEAAAAAAAAAuUnaAAAAAADDXNsAAAAAAP5xdGUAAAAAa4BQirD//////////////6fVQmsAAAAAAAAAAAAAAACar9SsB0sAAAAAAAAAAAAAAAAAAAAAAABBXO7/SWwLAAAAAAAAAAAAa0vYrBqvCwAAAAAAAAAAACaTDwAAAAAAAAAAAAAAAACHRTA1zkYLAAAAAAAAAAAAEkQuep2/CwAAAAAAAAAAAFAYOQmCjQsAAAAAAAAAAAC9r80AAAAAAAAAAAAAAAAANYB5EXeYCwAAAAAAAAAAAADqjJbciAAAAAAAAAAAAAAANiZLB47/////////////rEGjW00WAAAAAAAAAAAAAFTeD4aWAAAAAAAAAAAAAAAAQGNSv8YBAAAAAAAAAAAAUt/uyv7//////////////802zJqt/v/////////////PSTYa2wAAAAAAAAAAAAAAtPcalqL+/////////////xvHbwvuAAAAAAAAAAAAAAAAdsrWtPEAAAAAAAAAAAAAcbUT//////9xtRP//////3G1E///////Csx3AAAAAACVwjw2OgAAAAAAAAAAAAAAd/FNszYAAAAAAAAAAAAAALHQnZIDAAAAAAAAAAAAAAAA8z1QCQAAAAAAAAAAAAAAwY+XFgAAAAAAAAAAAAAAAEFTL9MIAAAAAAAAAAAAAAAHWeRpAAAAAAAAAAAAAAAAB1nkaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQlAeGCeEKAAAAAAAAAAAAME8Wz6hEDAAAAAAAAAAAABctSD9BbwsAAAAAAAAAAAA8T/PdEqwLAAAAAAAAAAAAMMvbAAAAAADpTP///////6NCywAAAAAA0yfeAAAAAAA7tdQAAAAAAJ3u2wAAAAAAwI8ADgAAAABrBAAAAAAAAA98N2D9////MTx0ZQAAAAAQDgAAAAAAAADKmjsAAAAAZAAAAAAAAAAA8gUqAQAAAAAAAAAAAAAA/9iJIUQBAAB7ga9oBQAAAADrzocBAAAAxXF0ZQAAAACI1QcAAAAAAHeBAQAAAAAA/nF0ZQAAAACUEQAAoIYBALV+AQDrBwAAAAAAAAAAAABkADIAZMgEAQAAAAAEAAAACvtTAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZUL9UG/wAAAAAAAAAAAAAAAAAAAAAAADFNQk9OSy1QRVJQICAgICAgICAgICAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQNAgAAAAAA5xkAAAAAAACMAgAAAAAAACYCAADuAgAA+CQBAPgkAQDECQAA3AUAAAAAAAAQJwAAAgIAABwDAAAEAAIAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="); + let mut decoded_bytes = base64::decode(perp_market_str).unwrap(); + let perp_market_bytes = decoded_bytes.as_mut_slice(); + + let key = Pubkey::from_str("2QeqpeJUVo2LBWNELRfcBwJgrNoxJQSd7gokcaM5nvaa").unwrap(); + let owner = Pubkey::from_str("dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH").unwrap(); + let mut lamports = 0; + let perp_market_account_info = + create_account_info(&key, true, &mut lamports, perp_market_bytes, &owner); + let market_map = PerpMarketMap::load_one(&perp_market_account_info, true).unwrap(); let oracle_market_str = String::from("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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); let mut decoded_bytes = base64::decode(oracle_market_str).unwrap(); @@ -1401,7 +1450,9 @@ fn update_amm_near_boundary2() { let now = 1702120657; let mut oracle_map = OracleMap::load_one(&jto_market_account_info, slot, None).unwrap(); - let mut perp_market = perp_market_loader.load_mut().unwrap(); + // let perp_market_old = market_map.get_ref(&4).unwrap(); + + let mut perp_market = market_map.get_ref_mut(&4).unwrap(); println!("perp_market: {:?}", perp_market.amm.last_update_slot); @@ -1412,4 +1463,260 @@ fn update_amm_near_boundary2() { let cost = _update_amm(&mut perp_market, oracle_price_data, &state, now, slot).unwrap(); assert_eq!(cost, 2987010); + + let inv = perp_market.amm.base_asset_amount_with_amm; + assert_eq!(inv, 24521505718700); + + let (_, _, r1_orig, r2_orig) = calculate_base_swap_output_with_spread( + &perp_market.amm, + inv.unsigned_abs() as u64, + swap_direction_to_close_position(inv), + ) + .unwrap(); + + assert_eq!(r1_orig, 334837204625); + assert_eq!(r2_orig, 703359043); + + let current_k = perp_market.amm.sqrt_k; + let _current_peg = perp_market.amm.peg_multiplier; + + let new_k = (current_k * 900000) / 100; + recenter_perp_market_amm(&mut perp_market.amm, oracle_price_data.price as u128, new_k).unwrap(); + + assert_eq!(perp_market.amm.sqrt_k, new_k); + assert_eq!( + perp_market.amm.peg_multiplier, + oracle_price_data.price as u128 + ); + + let (_r1, _r2) = swap_base_asset( + &mut perp_market, + inv.unsigned_abs() as u64, + swap_direction_to_close_position(inv), + ) + .unwrap(); + + // assert_eq!(r1, r1_orig); // 354919762322 w/o k adj + // assert_eq!(r2, r2_orig as i64); + + // assert_eq!(perp_market.amm.peg_multiplier, current_peg); +} + +#[test] +fn recenter_amm_2() { + // sui example + let perp_market_str: String = String::from("Ct8MLGv1N/d29jnnLxPJWcgnELd2ICWqe/HjfUfvrt/0yq7vt4ipySPXMVET9bHTunqDYExEuU159P1pr3f4BPx/kgptxldEbY8QAAAAAAAAAAAAAAAAAAMAAAAAAAAABb8QAAAAAADCjBAAAAAAANnvrmUAAAAAA/UzhKT1/////////////+zWKQkDAAAAAAAAAAAAAADXxsbXggQAAAAAAAAAAAAAAAAAAAAAAAAm1aGXXBcBAAAAAAAAAAAA0bqOq60ZeX0DAAAAAAAAADxrEgAAAAAAAAAAAAAAAABWUcGPbucAAAAAAAAAAAAAixe+mDdRAQAAAAAAAAAAAAHgQW8bmvMBAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAObJUKUBReX0DAAAAAAAAAAB82Wd71QAAAAAAAAAAAAAAvJautCf/////////////zNCf7v///////////////zRn0Ccw/f////////////8AAI1J/RoHAAAAAAAAAAAA2TrFMQwAAAAAAAAAAAAAAIasEJrH//////////////8CQy3yOAAAAAAAAAAAAAAA/Bzf4Mb//////////////9dAQLc5AAAAAAAAAAAAAAAA4EFvG5rzAQAAAAAAAAAA0Qb////////RBv///////9EG////////JaIAAAAAAADuHq3oAQAAAAAAAAAAAAAAZZBlmf///////////////2Y79WMCAAAAAAAAAAAAAACW6DzZ+f//////////////Ut/+OAEAAAAAAAAAAAAAAB0oBjUBAAAAAAAAAAAAAACR6S4LAAAAAAAAAAAAAAAAAOAtCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACn0WwwyBIBAAAAAAAAAAAAmOidoYFAXYwDAAAAAAAAAFSG6vGvFwEAAAAAAAAAAACRR6oTndNufAMAAAAAAAAAbosQAAAAAAAGdf///////1+cEAAAAAAARMEQAAAAAADRrhAAAAAAAH5MEAAAAAAA6EqDDgAAAADQAwAAAAAAAI007gAAAAAAQeauZQAAAAAQDgAAAAAAAADKmjsAAAAAZAAAAAAAAAAAypo7AAAAAAAAAAAAAAAAjPDu4DcAAAAXm1qdAAAAALcGYAwDAAAAiu6uZQAAAACqcwAAAAAAAJczAAAAAAAA2e+uZQAAAACIEwAAPHMAAOKBAAAYCQAAAAAAAKEHAABkADIAZMgAAQAAAAAEAAAATu+XBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3/spZrMwAAAAAAAAAAAAAAAAAAAAAAAFNVSS1QRVJQICAgICAgICAgICAgICAgICAgICAgICAgAOH1BQAAAAAA4fUFAAAAAADKmjsAAAAAiF7MCQAAAACH6a5lAAAAAADC6wsAAAAAAAAAAAAAAAAAAAAAAAAAAI0SAQAAAAAAbRgAAAAAAADDBgAAAAAAAMIBAADCAQAAECcAACBOAADoAwAA9AEAAAAAAAAQJwAAIAEAANEBAAAJAAEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="); + let mut decoded_bytes = base64::decode(perp_market_str).unwrap(); + let perp_market_bytes = decoded_bytes.as_mut_slice(); + + let key = Pubkey::from_str("91NsaUmTNNdLGbYtwmoiYSn9SgWHCsZiChfMYMYZ2nQx").unwrap(); + let owner = Pubkey::from_str("dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH").unwrap(); + let mut lamports = 0; + let perp_market_account_info = + create_account_info(&key, true, &mut lamports, perp_market_bytes, &owner); + let market_map = PerpMarketMap::load_one(&perp_market_account_info, true).unwrap(); + + // let oracle_market_str = String::from("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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + // let mut decoded_bytes = base64::decode(oracle_market_str).unwrap(); + // let oracle_market_bytes = decoded_bytes.as_mut_slice(); + + let mut oracle_price = get_hardcoded_pyth_price(1_120_000, 6); + let oracle_price_key = + Pubkey::from_str("3Qub3HaAJaa2xNY7SUqPKd3vVwTqDfDDkEUMPjXD2c1q").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + let mut data = get_account_bytes(&mut oracle_price); + let mut lamports2 = 0; + + let oracle_account_info = create_account_info( + &oracle_price_key, + true, + &mut lamports2, + &mut data[..], + &pyth_program, + ); + + //https://explorer.solana.com/block/243485436 + let slot = 243485436; + let now = 1705963488; + let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap(); + + // let perp_market_old = market_map.get_ref(&4).unwrap(); + + let mut perp_market = market_map.get_ref_mut(&9).unwrap(); + + println!( + "perp_market latest slot: {:?}", + perp_market.amm.last_update_slot + ); + + // previous values + assert_eq!(perp_market.amm.peg_multiplier, 5); + assert_eq!(perp_market.amm.quote_asset_reserve, 64381518181749930705); + assert_eq!(perp_market.amm.base_asset_reserve, 307161425106214); + + let oracle_price_data = oracle_map.get_price_data(&oracle_price_key).unwrap(); + + let state = State::default(); + + let cost = _update_amm(&mut perp_market, oracle_price_data, &state, now, slot).unwrap(); + + assert_eq!(cost, 0); + + let inv = perp_market.amm.base_asset_amount_with_amm; + assert_eq!(inv, -291516212); + + let (_, _, r1_orig, r2_orig) = calculate_base_swap_output_with_spread( + &perp_market.amm, + inv.unsigned_abs() as u64, + swap_direction_to_close_position(inv), + ) + .unwrap(); + + assert_eq!(r1_orig, 326219); + assert_eq!(r2_orig, 20707); + + let current_k = perp_market.amm.sqrt_k; + let _current_peg = perp_market.amm.peg_multiplier; + let new_k = current_k * 2; + + // refusal to decrease further + assert_eq!(current_k, current_k); + assert_eq!(perp_market.amm.user_lp_shares, current_k - 1); + assert_eq!(perp_market.amm.get_lower_bound_sqrt_k().unwrap(), current_k); + + recenter_perp_market_amm(&mut perp_market.amm, oracle_price_data.price as u128, new_k).unwrap(); + + assert_eq!(perp_market.amm.sqrt_k, new_k); + assert_eq!( + perp_market.amm.peg_multiplier, + oracle_price_data.price as u128 + ); + assert_eq!(perp_market.amm.peg_multiplier, 1_120_000); + // assert_eq!(perp_market.amm.quote_asset_reserve, 140625455708483789 * 2); + // assert_eq!(perp_market.amm.base_asset_reserve, 140625456291516213 * 2); + assert_eq!(perp_market.amm.base_asset_reserve, 281250912291516214); + assert_eq!(perp_market.amm.quote_asset_reserve, 281250911708483790); + + crate::validation::perp_market::validate_perp_market(&perp_market).unwrap(); + + let (r1, r2) = swap_base_asset( + &mut perp_market, + inv.unsigned_abs() as u64, + swap_direction_to_close_position(inv), + ) + .unwrap(); + + // adjusted slightly + assert_eq!(r1, 348628); // 354919762322 w/o k adj + assert_eq!(r2, 22129); + + let new_scale = 2; + let new_sqrt_k = perp_market.amm.sqrt_k * new_scale; + let update_k_result = get_update_k_result(&perp_market, U192::from(new_sqrt_k), false).unwrap(); + let adjustment_cost = adjust_k_cost(&mut perp_market, &update_k_result).unwrap(); + assert_eq!(adjustment_cost, 0); + + update_k(&mut perp_market, &update_k_result).unwrap(); + + // higher lower bound now + assert_eq!(perp_market.amm.sqrt_k, new_sqrt_k); + assert_eq!(perp_market.amm.user_lp_shares, current_k - 1); + assert!(perp_market.amm.get_lower_bound_sqrt_k().unwrap() > current_k); + assert_eq!( + perp_market.amm.get_lower_bound_sqrt_k().unwrap(), + 140766081456000000 + ); + // assert_eq!(perp_market.amm.peg_multiplier, current_peg); +} + +#[test] +fn test_move_amm() { + // sui example + let perp_market_str: String = String::from("Ct8MLGv1N/d29jnnLxPJWcgnELd2ICWqe/HjfUfvrt/0yq7vt4ipySPXMVET9bHTunqDYExEuU159P1pr3f4BPx/kgptxldEbY8QAAAAAAAAAAAAAAAAAAMAAAAAAAAABb8QAAAAAADCjBAAAAAAANnvrmUAAAAAA/UzhKT1/////////////+zWKQkDAAAAAAAAAAAAAADXxsbXggQAAAAAAAAAAAAAAAAAAAAAAAAm1aGXXBcBAAAAAAAAAAAA0bqOq60ZeX0DAAAAAAAAADxrEgAAAAAAAAAAAAAAAABWUcGPbucAAAAAAAAAAAAAixe+mDdRAQAAAAAAAAAAAAHgQW8bmvMBAAAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAObJUKUBReX0DAAAAAAAAAAB82Wd71QAAAAAAAAAAAAAAvJautCf/////////////zNCf7v///////////////zRn0Ccw/f////////////8AAI1J/RoHAAAAAAAAAAAA2TrFMQwAAAAAAAAAAAAAAIasEJrH//////////////8CQy3yOAAAAAAAAAAAAAAA/Bzf4Mb//////////////9dAQLc5AAAAAAAAAAAAAAAA4EFvG5rzAQAAAAAAAAAA0Qb////////RBv///////9EG////////JaIAAAAAAADuHq3oAQAAAAAAAAAAAAAAZZBlmf///////////////2Y79WMCAAAAAAAAAAAAAACW6DzZ+f//////////////Ut/+OAEAAAAAAAAAAAAAAB0oBjUBAAAAAAAAAAAAAACR6S4LAAAAAAAAAAAAAAAAAOAtCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACn0WwwyBIBAAAAAAAAAAAAmOidoYFAXYwDAAAAAAAAAFSG6vGvFwEAAAAAAAAAAACRR6oTndNufAMAAAAAAAAAbosQAAAAAAAGdf///////1+cEAAAAAAARMEQAAAAAADRrhAAAAAAAH5MEAAAAAAA6EqDDgAAAADQAwAAAAAAAI007gAAAAAAQeauZQAAAAAQDgAAAAAAAADKmjsAAAAAZAAAAAAAAAAAypo7AAAAAAAAAAAAAAAAjPDu4DcAAAAXm1qdAAAAALcGYAwDAAAAiu6uZQAAAACqcwAAAAAAAJczAAAAAAAA2e+uZQAAAACIEwAAPHMAAOKBAAAYCQAAAAAAAKEHAABkADIAZMgAAQAAAAAEAAAATu+XBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3/spZrMwAAAAAAAAAAAAAAAAAAAAAAAFNVSS1QRVJQICAgICAgICAgICAgICAgICAgICAgICAgAOH1BQAAAAAA4fUFAAAAAADKmjsAAAAAiF7MCQAAAACH6a5lAAAAAADC6wsAAAAAAAAAAAAAAAAAAAAAAAAAAI0SAQAAAAAAbRgAAAAAAADDBgAAAAAAAMIBAADCAQAAECcAACBOAADoAwAA9AEAAAAAAAAQJwAAIAEAANEBAAAJAAEAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="); + let mut decoded_bytes = base64::decode(perp_market_str).unwrap(); + let perp_market_bytes = decoded_bytes.as_mut_slice(); + + let key = Pubkey::from_str("91NsaUmTNNdLGbYtwmoiYSn9SgWHCsZiChfMYMYZ2nQx").unwrap(); + let owner = Pubkey::from_str("dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH").unwrap(); + let mut lamports = 0; + let perp_market_account_info = + create_account_info(&key, true, &mut lamports, perp_market_bytes, &owner); + let market_map = PerpMarketMap::load_one(&perp_market_account_info, true).unwrap(); + + // let oracle_market_str = String::from("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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + // let mut decoded_bytes = base64::decode(oracle_market_str).unwrap(); + // let oracle_market_bytes = decoded_bytes.as_mut_slice(); + + let mut oracle_price = get_hardcoded_pyth_price(1_120_000, 6); + let oracle_price_key = + Pubkey::from_str("3Qub3HaAJaa2xNY7SUqPKd3vVwTqDfDDkEUMPjXD2c1q").unwrap(); + let pyth_program = crate::ids::pyth_program::id(); + let mut data = get_account_bytes(&mut oracle_price); + let mut lamports2 = 0; + + let oracle_account_info = create_account_info( + &oracle_price_key, + true, + &mut lamports2, + &mut data[..], + &pyth_program, + ); + + //https://explorer.solana.com/block/243485436 + let slot = 243485436; + let now = 1705963488; + let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap(); + + // let perp_market_old = market_map.get_ref(&4).unwrap(); + + let mut perp_market = market_map.get_ref_mut(&9).unwrap(); + + println!( + "perp_market latest slot: {:?}", + perp_market.amm.last_update_slot + ); + + // previous values + assert_eq!(perp_market.amm.peg_multiplier, 5); + assert_eq!(perp_market.amm.quote_asset_reserve, 64381518181749930705); + assert_eq!(perp_market.amm.base_asset_reserve, 307161425106214); + + let oracle_price_data = oracle_map.get_price_data(&oracle_price_key).unwrap(); + + let state = State::default(); + + let cost = _update_amm(&mut perp_market, oracle_price_data, &state, now, slot).unwrap(); + + assert_eq!(cost, 0); + + let inv = perp_market.amm.base_asset_amount_with_amm; + assert_eq!(inv, -291516212); + + let (_, _, r1_orig, r2_orig) = calculate_base_swap_output_with_spread( + &perp_market.amm, + inv.unsigned_abs() as u64, + swap_direction_to_close_position(inv), + ) + .unwrap(); + + assert_eq!(r1_orig, 326219); + assert_eq!(r2_orig, 20707); + let current_bar = perp_market.amm.base_asset_reserve; + let _current_qar = perp_market.amm.quote_asset_reserve; + let current_k = perp_market.amm.sqrt_k; + let inc_numerator = BASE_PRECISION + BASE_PRECISION / 100; + let new_k = current_k * inc_numerator / BASE_PRECISION; + + // test correction + move_price( + &mut perp_market.amm, + current_bar * inc_numerator / BASE_PRECISION, + // current_qar * inc_numerator / BASE_PRECISION, + 65025333363567459347, // pass in exact amount that reconciles + new_k, + ) + .unwrap(); + crate::validation::perp_market::validate_perp_market(&perp_market).unwrap(); + assert_eq!(perp_market.amm.sqrt_k, new_k); + assert_eq!(perp_market.amm.peg_multiplier, 5); // still same } diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 0e13d6645..be5eeebba 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -867,6 +867,21 @@ pub fn handle_move_amm_price( Ok(()) } +#[access_control( + perp_market_valid(&ctx.accounts.perp_market) +)] +pub fn handle_recenter_perp_market_amm( + ctx: Context, + peg_multiplier: u128, + sqrt_k: u128, +) -> Result<()> { + let perp_market = &mut load_mut!(ctx.accounts.perp_market)?; + controller::amm::recenter_perp_market_amm(&mut perp_market.amm, peg_multiplier, sqrt_k)?; + validate_perp_market(perp_market)?; + + Ok(()) +} + #[access_control( perp_market_valid(&ctx.accounts.perp_market) )] @@ -1166,7 +1181,7 @@ pub fn handle_update_k(ctx: Context, sqrt_k: u128) -> Result<()> { let update_k_result = get_update_k_result(perp_market, new_sqrt_k_u192, true)?; - let adjustment_cost = math::cp_curve::adjust_k_cost(perp_market, &update_k_result)?; + let adjustment_cost: i128 = math::cp_curve::adjust_k_cost(perp_market, &update_k_result)?; math::cp_curve::update_k(perp_market, &update_k_result)?; diff --git a/programs/drift/src/lib.rs b/programs/drift/src/lib.rs index 53acdbdc3..fd0cc2e3b 100644 --- a/programs/drift/src/lib.rs +++ b/programs/drift/src/lib.rs @@ -626,6 +626,14 @@ pub mod drift { handle_move_amm_price(ctx, base_asset_reserve, quote_asset_reserve, sqrt_k) } + pub fn recenter_perp_market_amm( + ctx: Context, + peg_multiplier: u128, + sqrt_k: u128, + ) -> Result<()> { + handle_recenter_perp_market_amm(ctx, peg_multiplier, sqrt_k) + } + pub fn update_perp_market_expiry( ctx: Context, expiry_ts: i64, diff --git a/programs/drift/src/math/repeg.rs b/programs/drift/src/math/repeg.rs index 0f9568031..54395f149 100644 --- a/programs/drift/src/math/repeg.rs +++ b/programs/drift/src/math/repeg.rs @@ -284,11 +284,14 @@ pub fn adjust_amm( let adjustment_cost: i128 = if adjust_k && can_lower_k { // TODO can be off by 1? + // always let protocol-owned sqrt_k be either least .1% of lps or the base amount / min order + let new_sqrt_k_lower_bound = market.amm.get_lower_bound_sqrt_k()?; + let new_sqrt_k = market .amm .sqrt_k .safe_sub(market.amm.sqrt_k.safe_div(1000)?)? - .max(market.amm.user_lp_shares.safe_add(1)?); + .max(new_sqrt_k_lower_bound); let update_k_result = cp_curve::get_update_k_result(market, bn::U192::from(new_sqrt_k), true)?; diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 862a7ee23..9fed14bbd 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -826,6 +826,15 @@ impl AMM { } } + pub fn get_lower_bound_sqrt_k(self) -> DriftResult { + Ok(self.sqrt_k.min( + self.user_lp_shares + .safe_add(self.user_lp_shares.safe_div(1000)?)? + .max(self.min_order_size.cast()?) + .max(self.base_asset_amount_with_amm.unsigned_abs().cast()?), + )) + } + pub fn get_protocol_owned_position(self) -> DriftResult { self.base_asset_amount_with_amm .safe_add(self.base_asset_amount_with_unsettled_lp)? diff --git a/sdk/src/adminClient.ts b/sdk/src/adminClient.ts index 2170ffbf7..22f8b523c 100644 --- a/sdk/src/adminClient.ts +++ b/sdk/src/adminClient.ts @@ -358,6 +358,32 @@ export class AdminClient extends DriftClient { return txSig; } + public async recenterPerpMarketAmm( + perpMarketIndex: number, + pegMultiplier: BN, + sqrtK: BN + ): Promise { + const marketPublicKey = await getPerpMarketPublicKey( + this.program.programId, + perpMarketIndex + ); + + const tx = await this.program.transaction.recenterPerpMarketAmm( + pegMultiplier, + sqrtK, + { + accounts: { + state: await this.getStatePublicKey(), + admin: this.wallet.publicKey, + perpMarket: marketPublicKey, + }, + } + ); + + const { txSig } = await this.sendTransaction(tx, [], this.opts); + return txSig; + } + public async updatePerpMarketConcentrationScale( perpMarketIndex: number, concentrationScale: BN diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index a67ea7e1c..d8d05d76b 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -2972,6 +2972,36 @@ } ] }, + { + "name": "recenterPerpMarketAmm", + "accounts": [ + { + "name": "admin", + "isMut": false, + "isSigner": true + }, + { + "name": "state", + "isMut": false, + "isSigner": false + }, + { + "name": "perpMarket", + "isMut": true, + "isSigner": false + } + ], + "args": [ + { + "name": "pegMultiplier", + "type": "u128" + }, + { + "name": "sqrtK", + "type": "u128" + } + ] + }, { "name": "updatePerpMarketExpiry", "accounts": [ diff --git a/sdk/tests/amm/test.ts b/sdk/tests/amm/test.ts index abc75015f..d83d38401 100644 --- a/sdk/tests/amm/test.ts +++ b/sdk/tests/amm/test.ts @@ -31,6 +31,7 @@ import { ContractTier, isOracleValid, OracleGuardRails, + // calculateReservePrice, } from '../../src'; import { mockPerpMarkets } from '../dlob/helpers'; @@ -437,11 +438,242 @@ describe('AMM Tests', () => { true ); - console.log(terms2); + // console.log(terms2); assert(terms2.effectiveLeverageCapped <= 1.000001); assert(terms2.inventorySpreadScale == 1.0306); assert(terms2.longSpread == 515); assert(terms2.shortSpread == 5668); + + const suiExample = { + status: 'active', + contractType: 'perpetual', + contractTier: 'c', + expiryTs: '0', + expiryPrice: '0', + marketIndex: 9, + pubkey: '91NsaUmTNNdLGbYtwmoiYSn9SgWHCsZiChfMYMYZ2nQx', + name: 'SUI-PERP', + amm: { + baseAssetReserve: '234381482764434', + sqrtK: '109260723000000001', + lastFundingRate: '-16416', + lastFundingRateTs: '1705845755', + lastMarkPriceTwap: '1105972', + lastMarkPriceTwap5Min: '1101202', + lastMarkPriceTwapTs: '1705846920', + lastTradeTs: '1705846920', + oracle: '3Qub3HaAJaa2xNY7SUqPKd3vVwTqDfDDkEUMPjXD2c1q', + oracleSource: 'pyth', + historicalOracleData: { + lastOraclePrice: '1099778', + lastOracleDelay: '2', + lastOracleConf: '0', + lastOraclePriceTwap: '1106680', + lastOraclePriceTwap5Min: '1102634', + lastOraclePriceTwapTs: '1705846920', + }, + lastOracleReservePriceSpreadPct: '-262785', + lastOracleConfPct: '1359', + fundingPeriod: '3600', + quoteAssetReserve: '50933655038273508156', + pegMultiplier: '4', + cumulativeFundingRateLong: '186069301', + cumulativeFundingRateShort: '186007157', + last24HAvgFundingRate: '35147', + lastFundingRateShort: '-16416', + lastFundingRateLong: '-16416', + totalLiquidationFee: '4889264000', + totalFeeMinusDistributions: '-29523583393', + totalFeeWithdrawn: '5251194706', + totalFee: '7896066035', + totalFeeEarnedPerLp: '77063238', + userLpShares: '109260723000000000', + baseAssetAmountWithUnsettledLp: '-762306519581', + orderStepSize: '1000000000', + orderTickSize: '100', + maxFillReserveFraction: '100', + maxSlippageRatio: '50', + baseSpread: '5000', + curveUpdateIntensity: '100', + baseAssetAmountWithAmm: '306519581', + baseAssetAmountLong: '223405000000000', + baseAssetAmountShort: '-224167000000000', + quoteAssetAmount: '57945607973', + terminalQuoteAssetReserve: '50933588428309274920', + concentrationCoef: '1207100', + feePool: '[object Object]', + totalExchangeFee: '10110336057', + totalMmFee: '-1870961568', + netRevenueSinceLastFunding: '-141830281', + lastUpdateSlot: '243204071', + lastOracleNormalisedPrice: '1098594', + lastOracleValid: 'true', + lastBidPriceTwap: '1105864', + lastAskPriceTwap: '1106081', + longSpread: '259471', + shortSpread: '3314', + maxSpread: '29500', + baseAssetAmountPerLp: '-11388426214145', + quoteAssetAmountPerLp: '13038990874', + targetBaseAssetAmountPerLp: '0', + ammJitIntensity: '200', + maxOpenInterest: '2000000000000000', + maxBaseAssetReserve: '282922257844734', + minBaseAssetReserve: '194169322578092', + totalSocialLoss: '0', + quoteBreakEvenAmountLong: '-237442196125', + quoteBreakEvenAmountShort: '243508341566', + quoteEntryAmountLong: '-234074123777', + quoteEntryAmountShort: '240215285058', + markStd: '237945', + oracleStd: '8086', + longIntensityCount: '0', + longIntensityVolume: '162204', + shortIntensityCount: '995', + shortIntensityVolume: '2797331131', + volume24H: '91370028405', + minOrderSize: '1000000000', + maxPositionSize: '0', + bidBaseAssetReserve: '234770820775670', + bidQuoteAssetReserve: '50849187948657797529', + askBaseAssetReserve: '205083797418879', + askQuoteAssetReserve: '58209891472312580749', + perLpBase: '4', + }, + numberOfUsersWithBase: '279', + numberOfUsers: '436', + marginRatioInitial: '1000', + marginRatioMaintenance: '500', + nextFillRecordId: '69433', + nextFundingRateRecordId: '6221', + nextCurveRecordId: '1731', + pnlPool: { + scaledBalance: '61514197782399', + marketIndex: '0', + }, + liquidatorFee: '10000', + ifLiquidationFee: '20000', + imfFactor: '450', + unrealizedPnlImfFactor: '450', + unrealizedPnlMaxImbalance: '200000000', + unrealizedPnlInitialAssetWeight: '0', + unrealizedPnlMaintenanceAssetWeight: '10000', + insuranceClaim: { + revenueWithdrawSinceLastSettle: '100000000', + maxRevenueWithdrawPerPeriod: '100000000', + lastRevenueWithdrawTs: '1705846454', + quoteSettledInsurance: '164388488', + quoteMaxInsurance: '1000000000', + }, + quoteSpotMarketIndex: '0', + feeAdjustment: '0', + }; + + const reservePrice = calculatePrice( + new BN(suiExample.amm.baseAssetReserve), + new BN(suiExample.amm.quoteAssetReserve), + new BN(suiExample.amm.pegMultiplier) + ); + console.log('reservePrice', reservePrice.toString()); + assert(reservePrice.eq(new BN('869243'))); + + const reservePriceMod = calculatePrice( + new BN(suiExample.amm.baseAssetReserve), + new BN(suiExample.amm.quoteAssetReserve), + new BN(suiExample.amm.pegMultiplier).add(ONE) + ); + console.log('reservePriceMod', reservePriceMod.toString()); + assert(reservePriceMod.eq(new BN('1086554'))); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const termsSuiExample: AMMSpreadTerms = calculateSpreadBN( + Number(suiExample.amm.baseSpread.toString()), + new BN(suiExample.amm.lastOracleReservePriceSpreadPct), + new BN(suiExample.amm.lastOracleConfPct), + Number(suiExample.amm.maxSpread.toString()), + new BN(suiExample.amm.quoteAssetReserve), + new BN(suiExample.amm.terminalQuoteAssetReserve), + new BN(suiExample.amm.pegMultiplier), + new BN(suiExample.amm.baseAssetAmountWithAmm), + reservePrice, // reserve price + new BN(suiExample.amm.totalFeeMinusDistributions), + new BN(suiExample.amm.netRevenueSinceLastFunding), + new BN(suiExample.amm.baseAssetReserve), + new BN(suiExample.amm.minBaseAssetReserve), + new BN(suiExample.amm.maxBaseAssetReserve), + new BN(suiExample.amm.markStd), + new BN(suiExample.amm.oracleStd), + new BN(suiExample.amm.longIntensityVolume), + new BN(suiExample.amm.shortIntensityVolume), + new BN(suiExample.amm.volume24H), + true + ); + + // console.log(termsSuiExample); + assert(termsSuiExample.effectiveLeverageCapped <= 1.000001); + assert(termsSuiExample.inventorySpreadScale == 1.00007); + assert(termsSuiExample.longSpread == 259073); + assert(termsSuiExample.shortSpread == 3712); + + // reset amm reserves/peg to balanced values s.t. liquidity/price is the same + // to avoid error prone int math + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const termsSuiExampleMod1: AMMSpreadTerms = calculateSpreadBN( + Number(suiExample.amm.baseSpread.toString()), + ZERO, + new BN(suiExample.amm.lastOracleConfPct), + Number(suiExample.amm.maxSpread.toString()), + new BN(suiExample.amm.quoteAssetReserve), + new BN(suiExample.amm.terminalQuoteAssetReserve), + new BN(suiExample.amm.pegMultiplier), + new BN(suiExample.amm.baseAssetAmountWithAmm), + reservePriceMod, // reserve price + new BN(suiExample.amm.totalFeeMinusDistributions), + new BN(suiExample.amm.netRevenueSinceLastFunding), + new BN(suiExample.amm.baseAssetReserve), + new BN(suiExample.amm.minBaseAssetReserve), + new BN(suiExample.amm.maxBaseAssetReserve), + new BN(suiExample.amm.markStd), + new BN(suiExample.amm.oracleStd), + new BN(suiExample.amm.longIntensityVolume), + new BN(suiExample.amm.shortIntensityVolume), + new BN(suiExample.amm.volume24H), + true + ); + console.log(termsSuiExampleMod1); + + // todo: add sdk recenter function? + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const termsSuiExampleMod2: AMMSpreadTerms = calculateSpreadBN( + Number(suiExample.amm.baseSpread.toString()), + ZERO, + new BN(suiExample.amm.lastOracleConfPct), + Number(suiExample.amm.maxSpread.toString()), + new BN(suiExample.amm.sqrtK), + new BN(suiExample.amm.terminalQuoteAssetReserve), + reservePriceMod, // peg + new BN(suiExample.amm.baseAssetAmountWithAmm), + reservePriceMod, // reserve price + new BN(suiExample.amm.totalFeeMinusDistributions), + new BN(suiExample.amm.netRevenueSinceLastFunding), + new BN(suiExample.amm.sqrtK), + new BN(suiExample.amm.sqrtK.sub()), + new BN(suiExample.amm.maxBaseAssetReserve), + new BN(suiExample.amm.markStd), + new BN(suiExample.amm.oracleStd), + new BN(suiExample.amm.longIntensityVolume), + new BN(suiExample.amm.shortIntensityVolume), + new BN(suiExample.amm.volume24H), + true + ); + + console.log(termsSuiExampleMod2); + // assert(_.isEqual(termsSuiExampleMod2, termsSuiExampleMod1)); }); it('Spread Reserves (with offset)', () => {