Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

able to increase position #22

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 4 additions & 4 deletions programs/perpetuals/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ pub mod set_test_time;
pub mod test_init;

// public instructions
pub mod add_collateral;
pub mod add_liquidity;
pub mod close_position;
pub mod decrease_position;
pub mod get_add_liquidity_amount_and_fee;
pub mod get_assets_under_management;
pub mod get_entry_price_and_fee;
Expand All @@ -30,9 +30,9 @@ pub mod get_oracle_price;
pub mod get_pnl;
pub mod get_remove_liquidity_amount_and_fee;
pub mod get_swap_amount_and_fees;
pub mod increase_position;
pub mod liquidate;
pub mod open_position;
pub mod remove_collateral;
pub mod remove_liquidity;
pub mod swap;

Expand All @@ -53,9 +53,9 @@ pub use set_test_oracle_price::*;
pub use set_test_time::*;
pub use test_init::*;

pub use add_collateral::*;
pub use add_liquidity::*;
pub use close_position::*;
pub use decrease_position::*;
pub use get_add_liquidity_amount_and_fee::*;
pub use get_assets_under_management::*;
pub use get_entry_price_and_fee::*;
Expand All @@ -66,8 +66,8 @@ pub use get_oracle_price::*;
pub use get_pnl::*;
pub use get_remove_liquidity_amount_and_fee::*;
pub use get_swap_amount_and_fees::*;
pub use increase_position::*;
pub use liquidate::*;
pub use open_position::*;
pub use remove_collateral::*;
pub use remove_liquidity::*;
pub use swap::*;
3 changes: 2 additions & 1 deletion programs/perpetuals/src/instructions/close_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ pub fn close_position(ctx: Context<ClosePosition>, params: &ClosePositionParams)
custody.pricing.use_ema,
)?;

let exit_price = pool.get_exit_price(&token_price, &token_ema_price, position.side, custody)?;
let (_, exit_price) =
pool.get_exit_price(&token_price, &token_ema_price, position.side, custody)?;
msg!("Exit price: {}", exit_price);

if position.side == Side::Long {
Expand Down
297 changes: 297 additions & 0 deletions programs/perpetuals/src/instructions/decrease_position.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
//! DecreasePosition instruction handler

use {
crate::{
error::PerpetualsError,
math,
state::{
custody::Custody,
oracle::OraclePrice,
perpetuals::Perpetuals,
pool::Pool,
position::{Position, Side},
},
},
anchor_lang::prelude::*,
anchor_spl::token::{Token, TokenAccount},
solana_program::program_error::ProgramError,
};

#[derive(Accounts)]
#[instruction(params: DecreasePositionParams)]
pub struct DecreasePosition<'info> {
#[account(mut)]
pub owner: Signer<'info>,

#[account(
mut,
constraint = receiving_account.mint == custody.mint,
has_one = owner
)]
pub receiving_account: Box<Account<'info, TokenAccount>>,

/// CHECK: empty PDA, authority for token accounts
#[account(
seeds = [b"transfer_authority"],
bump = perpetuals.transfer_authority_bump
)]
pub transfer_authority: AccountInfo<'info>,

#[account(
seeds = [b"perpetuals"],
bump = perpetuals.perpetuals_bump
)]
pub perpetuals: Box<Account<'info, Perpetuals>>,

#[account(
mut,
seeds = [b"pool",
pool.name.as_bytes()],
bump = pool.bump
)]
pub pool: Box<Account<'info, Pool>>,

#[account(
mut,
has_one = owner,
seeds = [b"position",
owner.key().as_ref(),
pool.key().as_ref(),
custody.key().as_ref(),
&[position.side as u8]],
bump = position.bump
)]
pub position: Box<Account<'info, Position>>,

#[account(
mut,
seeds = [b"custody",
pool.key().as_ref(),
custody.mint.as_ref()],
bump = custody.bump
)]
pub custody: Box<Account<'info, Custody>>,

/// CHECK: oracle account for the collateral token
#[account(
constraint = custody_oracle_account.key() == custody.oracle.oracle_account
)]
pub custody_oracle_account: AccountInfo<'info>,

#[account(
mut,
seeds = [b"custody_token_account",
pool.key().as_ref(),
custody.mint.as_ref()],
bump = custody.token_account_bump
)]
pub custody_token_account: Box<Account<'info, TokenAccount>>,

token_program: Program<'info, Token>,
}

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct DecreasePositionParams {
pub price: u64,
pub collateral_usd: u64,
pub size_usd: u64,
}

pub fn decrease_position(
ctx: Context<DecreasePosition>,
params: &DecreasePositionParams,
) -> Result<()> {
// check permissions
msg!("Check permissions");
let perpetuals = ctx.accounts.perpetuals.as_mut();
let custody = ctx.accounts.custody.as_mut();
if params.collateral_usd > 0 {
require!(
perpetuals.permissions.allow_collateral_withdrawal
&& custody.permissions.allow_collateral_withdrawal,
PerpetualsError::InstructionNotAllowed
);
}
if params.size_usd > 0 {
require!(
perpetuals.permissions.allow_close_position && custody.permissions.allow_close_position,
PerpetualsError::InstructionNotAllowed
);
}

// validate inputs
msg!("Validate inputs");
let position = ctx.accounts.position.as_mut();
if params.collateral_usd == 0 && (params.price == 0 || params.size_usd == 0) {
return Err(ProgramError::InvalidArgument.into());
}
if params.collateral_usd > 0
&& params.collateral_usd >= (position.size_usd - position.collateral_usd)
{
return Err(ProgramError::InvalidArgument.into());
}
if params.size_usd >= position.size_usd {
return Err(ProgramError::InvalidArgument.into());
}
let pool = ctx.accounts.pool.as_mut();
let token_id = pool.get_token_id(&custody.key())?;

// compute position price
let curtime = perpetuals.get_time()?;

let token_price = OraclePrice::new_from_oracle(
custody.oracle.oracle_type,
&ctx.accounts.custody_oracle_account.to_account_info(),
custody.oracle.max_price_error,
custody.oracle.max_price_age_sec,
curtime,
false,
)?;

let token_ema_price = OraclePrice::new_from_oracle(
custody.oracle.oracle_type,
&ctx.accounts.custody_oracle_account.to_account_info(),
custody.oracle.max_price_error,
custody.oracle.max_price_age_sec,
curtime,
custody.pricing.use_ema,
)?;

let (exit_oracle, exit_price) =
pool.get_exit_price(&token_price, &token_ema_price, position.side, custody)?;
msg!("Exit price: {}", exit_price);

if position.side == Side::Long {
require_gte!(exit_price, params.price, PerpetualsError::MaxPriceSlippage);
} else {
require_gte!(params.price, exit_price, PerpetualsError::MaxPriceSlippage);
}
custody.remove_position(position, curtime)?;

// compute fee
let (profit_usd, loss_usd) = pool.get_pnl_usd_for_size(
position,
&token_price,
&token_ema_price,
custody,
params.size_usd,
)?;
let mut transfer_amount = params.collateral_usd;
if profit_usd > 0 {
transfer_amount = math::checked_add(transfer_amount, profit_usd)?;
} else if loss_usd > 0 {
let loss_token = exit_oracle.get_token_amount(loss_usd, custody.decimals)?;

position.collateral_usd = math::checked_sub(position.collateral_usd, loss_usd)?;
position.collateral_amount = math::checked_sub(position.collateral_amount, loss_token)?;
}

let transfer_token_amount = exit_oracle.get_token_amount(transfer_amount, custody.decimals)?;
let fee_amount = pool.get_exit_fee(
token_id,
transfer_token_amount,
position.size_usd,
custody,
&exit_oracle,
)?;
let protocol_fee = Pool::get_fee_amount(custody.fees.protocol_share, fee_amount)?;

msg!("Collected fee: {}", fee_amount);

// compute amount to transfer
let transfer_amount = math::checked_sub(transfer_token_amount, fee_amount)?;
if transfer_amount > position.collateral_amount {
return Err(ProgramError::InsufficientFunds.into());
}
msg!("Amount out: {}", transfer_amount);

// update existing position
msg!("Update existing position");
let size = token_price.get_token_amount(params.size_usd, custody.decimals)?;
let release_locked_amount = math::checked_as_u64(math::checked_div(
math::checked_mul(size as u128, custody.pricing.max_payoff_mult as u128)?,
Perpetuals::BPS_POWER,
)?)?;

position.update_time = curtime;

if params.size_usd > 0 {
// calculate interest fee and reset cumulative_interest_snapshot
let interest_usd = custody.get_interest_amount_usd(position, curtime)?;
position.unrealized_loss_usd =
math::checked_add(position.unrealized_loss_usd, interest_usd)?;
position.cumulative_interest_snapshot = custody.get_cumulative_interest(curtime)?;

// (current size * price - removed size * new price) / (current size - removed size)
position.price = math::checked_as_u64(math::checked_div(
math::checked_sub(
math::checked_mul(position.size_usd as u128, position.price as u128)?,
math::checked_mul(params.size_usd as u128, exit_price as u128)?,
)?,
math::checked_sub(position.size_usd as u128, params.size_usd as u128)?,
)?)?;
position.size_usd = math::checked_sub(position.size_usd, params.size_usd)?;
position.locked_amount = math::checked_sub(position.locked_amount, release_locked_amount)?;
}

position.collateral_usd = math::checked_sub(position.collateral_usd, params.collateral_usd)?;

let collateral = exit_oracle.get_token_amount(params.collateral_usd, custody.decimals)?;
position.collateral_amount = math::checked_sub(position.collateral_amount, collateral)?;

// check position risk
msg!("Check position risks");
require!(
pool.check_leverage(token_id, position, &token_ema_price, custody, curtime, true)?,
PerpetualsError::MaxLeverage
);

// unlock funds
custody.unlock_funds(release_locked_amount)?;

// transfer tokens
msg!("Transfer tokens");
perpetuals.transfer_tokens(
ctx.accounts.custody_token_account.to_account_info(),
ctx.accounts.receiving_account.to_account_info(),
ctx.accounts.transfer_authority.to_account_info(),
ctx.accounts.token_program.to_account_info(),
transfer_amount,
)?;

// update custody stats
msg!("Update custody stats");
custody.collected_fees.close_position_usd = custody
.collected_fees
.close_position_usd
.wrapping_add(token_ema_price.get_asset_amount_usd(fee_amount, custody.decimals)?);
custody.volume_stats.close_position_usd = custody
.volume_stats
.close_position_usd
.wrapping_sub(params.size_usd);

let amount_lost = transfer_amount.saturating_sub(collateral);
custody.assets.owned = math::checked_sub(custody.assets.owned, amount_lost)?;
custody.assets.collateral = math::checked_sub(custody.assets.collateral, collateral)?;
custody.assets.protocol_fees = math::checked_add(custody.assets.protocol_fees, protocol_fee)?;

if position.side == Side::Long {
custody.trade_stats.oi_long_usd = custody
.trade_stats
.oi_long_usd
.saturating_sub(params.size_usd);
} else {
custody.trade_stats.oi_short_usd = custody
.trade_stats
.oi_short_usd
.saturating_sub(params.size_usd);
}

custody.trade_stats.profit_usd = custody.trade_stats.profit_usd.wrapping_add(profit_usd);
custody.trade_stats.loss_usd = custody.trade_stats.loss_usd.wrapping_add(loss_usd);

custody.add_position(position, &token_price, curtime)?;
custody.update_borrow_rate(curtime)?;

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ pub fn get_entry_price_and_fee(
custody.pricing.use_ema,
)?;

let entry_price = pool.get_entry_price(&token_price, &token_ema_price, params.side, custody)?;
let (_, entry_price) =
pool.get_entry_price(&token_price, &token_ema_price, params.side, custody)?;

let size_usd = token_price.get_asset_amount_usd(params.size, custody.decimals)?;
let collateral_usd = token_price.get_asset_amount_usd(params.collateral, custody.decimals)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ pub fn get_exit_price_and_fee(

let collateral = token_price.get_token_amount(position.collateral_usd, custody.decimals)?;

let price = pool.get_exit_price(&token_price, &token_ema_price, position.side, custody)?;
let (_, price) = pool.get_exit_price(&token_price, &token_ema_price, position.side, custody)?;

let fee = pool.get_exit_fee(
pool.get_token_id(&custody.key())?,
collateral,
position.size_usd,
custody,
&token_price,
)?;
Expand Down
Loading