triad-protocol 0.1.4

Triad protocol, trade solana projects
Documentation
use crate::constraints::{ is_authority_for_user_position, is_token_mint_for_vault };
use crate::errors::TriadProtocolError;
use crate::events::ClosePositionRecord;
use crate::state::Vault;
use crate::{ ClosePositionArgs, Position, Ticker, UserPosition };
use anchor_lang::prelude::*;
use anchor_spl::token::{ self, Token, TokenAccount, Transfer };

#[derive(Accounts)]
#[instruction(args: ClosePositionArgs)]
pub struct ClosePosition<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,

    #[account(mut)]
    pub ticker: Account<'info, Ticker>,

    #[account(mut)]
    pub vault: Account<'info, Vault>,

    #[account(
        mut,
        constraint = is_authority_for_user_position(&user_position, &signer)?,
    )]
    pub user_position: Account<'info, UserPosition>,

    #[account(mut)]
    pub vault_token_account: Account<'info, TokenAccount>,

    #[account(
        mut,
        token::authority = user_position.authority,
        token::mint = vault_token_account.mint,
        constraint = is_token_mint_for_vault(&vault_token_account.mint, &user_token_account.mint)?,
    )]
    pub user_token_account: Account<'info, TokenAccount>,

    pub system_program: Program<'info, System>,

    pub token_program: Program<'info, Token>,
}

pub fn close_position(ctx: Context<ClosePosition>, args: ClosePositionArgs) -> Result<()> {
    let user_position_cloned = ctx.accounts.user_position.clone();

    let current_pubkey_position = user_position_cloned.positions[args.position_index as usize];

    if current_pubkey_position.amount == 0 {
        return Err(TriadProtocolError::InvalidPosition.into());
    }

    let pnl =
        (ctx.accounts.ticker.price - current_pubkey_position.entry_price) *
        current_pubkey_position.amount;

    let new_amount = current_pubkey_position.amount + pnl;

    let seeds: &[&[&[u8]]] = &[
        &[b"vault", ctx.accounts.vault.ticker_address.as_ref(), &[ctx.accounts.vault.bump]],
    ];

    let transfer = token::transfer(
        CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            Transfer {
                from: ctx.accounts.vault_token_account.to_account_info(),
                to: ctx.accounts.user_token_account.to_account_info(),
                authority: ctx.accounts.vault.to_account_info(),
            },
            seeds
        ),
        new_amount - (new_amount * 10) / 1000
    );

    if transfer.is_err() {
        return Err(TriadProtocolError::InvalidWithdrawAmount.into());
    }

    let vault = &mut ctx.accounts.vault;

    if current_pubkey_position.is_long {
        vault.long_balance -= current_pubkey_position.amount;
        vault.long_positions_opened -= 1;
    } else {
        vault.short_balance -= current_pubkey_position.amount;
        vault.short_positions_opened -= 1;
    }

    let user_position = &mut ctx.accounts.user_position;

    vault.total_withdrawn += current_pubkey_position.amount;
    vault.net_withdraws += 1;

    user_position.total_withdrawn += current_pubkey_position.amount;
    user_position.lp_share -= current_pubkey_position.amount;

    user_position.positions[args.position_index as usize] = Position {
        amount: 0,
        entry_price: 0,
        ts: 0,
        is_long: false,
        is_open: false,
        pnl: 0,
    };

    emit!(ClosePositionRecord {
        ticker: ctx.accounts.vault.ticker_address,
        close_price: ctx.accounts.ticker.price,
        ts: Clock::get()?.unix_timestamp,
        pnl: (ctx.accounts.ticker.price as i64) - (current_pubkey_position.entry_price as i64),
        user: user_position.authority,
        amount: current_pubkey_position.amount,
        is_long: current_pubkey_position.is_long,
    });

    Ok(())
}