use crate::{
events::EventEmitter,
ops::market::MarketTransferOutOperation,
states::{
market::{
revertible::{Revertible, RevertibleMarket},
status::MarketStatus,
utils::ValidateMarketBalances,
},
Factor, HasMarketMeta,
},
ModelError,
};
use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, Token, TokenAccount};
use gmsol_model::{
num::Unsigned, price::Prices, BalanceExt, Bank, BaseMarketMut, LiquidityMarketExt,
PnlFactorKind, PoolExt,
};
use gmsol_utils::InitSpace;
use crate::{
constants,
states::{
market::config::{EntryArgs, MarketConfigBuffer},
Market, Seed, Store, TokenMapAccess, TokenMapHeader, TokenMapLoader,
},
utils::internal,
CoreError,
};
#[derive(Accounts)]
#[instruction(index_token_mint: Pubkey)]
pub struct InitializeMarket<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(has_one = token_map)]
pub store: AccountLoader<'info, Store>,
#[account(
init,
payer = authority,
mint::decimals = constants::MARKET_TOKEN_DECIMALS,
// We directly use the store as the authority.
mint::authority = store.key(),
seeds = [
constants::MAREKT_TOKEN_MINT_SEED,
store.key().as_ref(),
index_token_mint.as_ref(),
long_token_mint.key().as_ref(),
short_token_mint.key().as_ref(),
],
bump,
)]
pub market_token_mint: Account<'info, Mint>,
pub long_token_mint: Account<'info, Mint>,
pub short_token_mint: Account<'info, Mint>,
#[account(
init,
payer = authority,
space = 8 + Market::INIT_SPACE,
seeds = [
Market::SEED,
store.key().as_ref(),
market_token_mint.key().as_ref(),
],
bump,
)]
pub market: AccountLoader<'info, Market>,
#[account(has_one = store)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
#[account(
token::mint = long_token_mint,
// We use the store as the authority of the token account.
token::authority = store,
seeds = [
constants::MARKET_VAULT_SEED,
store.key().as_ref(),
long_token_mint.key().as_ref(),
],
bump,
)]
pub long_token_vault: Account<'info, TokenAccount>,
#[account(
token::mint = short_token_mint,
// We use the store as the authority of the token account.
token::authority = store,
seeds = [
constants::MARKET_VAULT_SEED,
store.key().as_ref(),
short_token_mint.key().as_ref(),
],
bump,
)]
pub short_token_vault: Account<'info, TokenAccount>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
pub(crate) fn unchecked_initialize_market(
ctx: Context<InitializeMarket>,
index_token_mint: Pubkey,
name: &str,
enable: bool,
) -> Result<()> {
{
let token_map = ctx.accounts.token_map.load_token_map()?;
require!(
token_map
.get(&index_token_mint)
.ok_or_else(|| error!(CoreError::NotFound))?
.is_enabled(),
CoreError::InvalidArgument
);
let long_token = &ctx.accounts.long_token_mint;
let long_token_config = token_map
.get(&long_token.key())
.ok_or_else(|| error!(CoreError::NotFound))?;
require!(
long_token_config.is_enabled(),
CoreError::TokenConfigDisabled
);
require!(
long_token_config.is_valid_pool_token_config(),
CoreError::InvalidArgument
);
require_eq!(
long_token_config.token_decimals(),
long_token.decimals,
CoreError::TokenDecimalsMismatched
);
let short_token = &ctx.accounts.short_token_mint;
let short_token_config = token_map
.get(&short_token.key())
.ok_or_else(|| error!(CoreError::NotFound))?;
require!(
short_token_config.is_enabled(),
CoreError::TokenConfigDisabled
);
require!(
short_token_config.is_valid_pool_token_config(),
CoreError::InvalidArgument
);
require_eq!(
short_token_config.token_decimals(),
short_token.decimals,
CoreError::TokenDecimalsMismatched
);
}
let market = &ctx.accounts.market;
market.load_init()?.init(
ctx.bumps.market,
ctx.accounts.store.key(),
name,
ctx.accounts.market_token_mint.key(),
index_token_mint,
ctx.accounts.long_token_mint.key(),
ctx.accounts.short_token_mint.key(),
enable,
)?;
Ok(())
}
impl<'info> internal::Authentication<'info> for InitializeMarket<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct ToggleMarket<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, has_one = store)]
pub market: AccountLoader<'info, Market>,
}
pub(crate) fn unchecked_toggle_market(ctx: Context<ToggleMarket>, enable: bool) -> Result<()> {
ctx.accounts.market.load_mut()?.set_enabled(enable);
Ok(())
}
impl<'info> internal::Authentication<'info> for ToggleMarket<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[event_cpi]
#[derive(Accounts)]
pub struct MarketTransferIn<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
pub from_authority: Signer<'info>,
#[account(mut, has_one = store)]
pub market: AccountLoader<'info, Market>,
#[account(mut, token::mint = vault.mint, constraint = from.key() != vault.key())]
pub from: Account<'info, TokenAccount>,
#[account(
mut,
token::authority = store,
seeds = [
constants::MARKET_VAULT_SEED,
store.key().as_ref(),
vault.mint.as_ref(),
],
bump,
)]
pub vault: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
pub(crate) fn unchecked_market_transfer_in(
ctx: Context<MarketTransferIn>,
amount: u64,
) -> Result<()> {
use anchor_spl::token;
{
let is_collateral_token = ctx
.accounts
.market
.load()?
.validated_meta(&ctx.accounts.store.key())?
.is_collateral_token(&ctx.accounts.from.mint);
require!(is_collateral_token, CoreError::InvalidArgument);
}
if amount != 0 {
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
token::Transfer {
from: ctx.accounts.from.to_account_info(),
to: ctx.accounts.vault.to_account_info(),
authority: ctx.accounts.from_authority.to_account_info(),
},
),
amount,
)?;
let event_emitter =
EventEmitter::new(&ctx.accounts.event_authority, ctx.bumps.event_authority);
let token = &ctx.accounts.vault.mint;
let mut market = RevertibleMarket::new(&ctx.accounts.market, event_emitter)?;
market
.record_transferred_in_by_token(token, &amount)
.map_err(ModelError::from)?;
market.commit();
}
Ok(())
}
impl<'info> internal::Authentication<'info> for MarketTransferIn<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct UpdateMarketConfig<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, has_one = store)]
pub market: AccountLoader<'info, Market>,
}
impl<'info> internal::Authentication<'info> for UpdateMarketConfig<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
pub(crate) fn unchecked_update_market_config(
ctx: Context<UpdateMarketConfig>,
key: &str,
value: Factor,
) -> Result<()> {
*ctx.accounts.market.load_mut()?.get_config_mut(key)? = value;
msg!(
"{}: set {} = {}",
ctx.accounts.market.load()?.meta.market_token_mint,
key,
value
);
Ok(())
}
pub(crate) fn unchecked_update_market_config_flag(
ctx: Context<UpdateMarketConfig>,
key: &str,
value: bool,
) -> Result<()> {
let previous = ctx
.accounts
.market
.load_mut()?
.set_config_flag(key, value)?;
msg!(
"{}: set {} = {}, previous = {}",
ctx.accounts.market.load()?.meta.market_token_mint,
key,
value,
previous,
);
Ok(())
}
#[derive(Accounts)]
pub struct UpdateMarketConfigWithBuffer<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, has_one = store)]
pub market: AccountLoader<'info, Market>,
#[account(mut, has_one = store, has_one = authority @ CoreError::PermissionDenied)]
pub buffer: Account<'info, MarketConfigBuffer>,
}
pub(crate) fn unchecked_update_market_config_with_buffer(
ctx: Context<UpdateMarketConfigWithBuffer>,
) -> Result<()> {
let buffer = &ctx.accounts.buffer;
require_gt!(
buffer.expiry,
Clock::get()?.unix_timestamp,
CoreError::InvalidArgument
);
ctx.accounts
.market
.load_mut()?
.update_config_with_buffer(buffer)?;
msg!(
"{} updated with buffer {}",
ctx.accounts.market.load()?.description()?,
buffer.key()
);
Ok(())
}
impl<'info> internal::Authentication<'info> for UpdateMarketConfigWithBuffer<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[derive(Accounts)]
pub struct ReadMarket<'info> {
pub market: AccountLoader<'info, Market>,
}
pub(crate) fn get_market_status(
ctx: Context<ReadMarket>,
prices: &Prices<u128>,
maximize_pnl: bool,
maximize_pool_value: bool,
) -> Result<MarketStatus> {
let market = ctx.accounts.market.load()?;
let status = MarketStatus::from_market(&market, prices, maximize_pnl, maximize_pool_value)
.map_err(ModelError::from)?;
Ok(status)
}
#[derive(Accounts)]
pub struct ReadMarketWithToken<'info> {
#[account(
constraint = market.load()?.meta.market_token_mint == market_token.key() @ CoreError::InvalidArgument,
)]
pub market: AccountLoader<'info, Market>,
pub market_token: Account<'info, Mint>,
}
pub(crate) fn get_market_token_price(
ctx: Context<ReadMarketWithToken>,
prices: &Prices<u128>,
pnl_factor: PnlFactorKind,
maximize: bool,
) -> Result<u128> {
let market = ctx.accounts.market.load()?;
let liquidity_market = market.as_liquidity_market(&ctx.accounts.market_token);
let price = liquidity_market
.market_token_price(prices, pnl_factor, maximize)
.map_err(ModelError::from)?;
Ok(price)
}
#[derive(Accounts)]
pub struct InitializeMarketConfigBuffer<'info> {
#[account(mut)]
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(init, payer = authority, space = 8 + MarketConfigBuffer::init_space(0))]
pub buffer: Account<'info, MarketConfigBuffer>,
pub system_program: Program<'info, System>,
}
pub(crate) fn initialize_market_config_buffer(
ctx: Context<InitializeMarketConfigBuffer>,
expire_after_secs: u32,
) -> Result<()> {
let buffer = &mut ctx.accounts.buffer;
buffer.authority = ctx.accounts.authority.key();
buffer.store = ctx.accounts.store.key();
buffer.expiry = Clock::get()?
.unix_timestamp
.saturating_add_unsigned(expire_after_secs as u64);
Ok(())
}
#[derive(Accounts)]
pub struct SetMarketConfigBufferAuthority<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut, has_one = authority @ CoreError::PermissionDenied)]
pub buffer: Account<'info, MarketConfigBuffer>,
}
pub(crate) fn set_market_config_buffer_authority(
ctx: Context<SetMarketConfigBufferAuthority>,
new_authority: Pubkey,
) -> Result<()> {
ctx.accounts.buffer.authority = new_authority;
Ok(())
}
#[derive(Accounts)]
pub struct CloseMarketConfigBuffer<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut, close = receiver, has_one = authority @ CoreError::PermissionDenied)]
pub buffer: Account<'info, MarketConfigBuffer>,
#[account(mut)]
pub receiver: UncheckedAccount<'info>,
}
pub(crate) fn close_market_config_buffer(_ctx: Context<CloseMarketConfigBuffer>) -> Result<()> {
Ok(())
}
#[derive(Accounts)]
#[instruction(new_configs: Vec<(String, Factor)>)]
pub struct PushToMarketConfigBuffer<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(
mut,
has_one = authority @ CoreError::PermissionDenied,
realloc = 8 + buffer.space_after_push(new_configs.len()),
realloc::payer = authority,
realloc::zero = false,
)]
pub buffer: Account<'info, MarketConfigBuffer>,
system_program: Program<'info, System>,
}
pub(crate) fn push_to_market_config_buffer(
ctx: Context<PushToMarketConfigBuffer>,
new_configs: Vec<EntryArgs>,
) -> Result<()> {
let buffer = &mut ctx.accounts.buffer;
for entry in new_configs {
buffer.push(entry.try_into()?);
}
Ok(())
}
#[derive(Accounts)]
pub struct ToggleGTMinting<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, has_one = store)]
pub market: AccountLoader<'info, Market>,
}
pub(crate) fn unchecked_toggle_gt_minting(
ctx: Context<ToggleGTMinting>,
enable: bool,
) -> Result<()> {
ctx.accounts
.market
.load_mut()?
.set_is_gt_minting_enbaled(enable);
Ok(())
}
impl<'info> internal::Authentication<'info> for ToggleGTMinting<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[event_cpi]
#[derive(Accounts)]
pub struct ClaimFeesFromMarket<'info> {
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, has_one = store)]
pub market: AccountLoader<'info, Market>,
pub token_mint: InterfaceAccount<'info, anchor_spl::token_interface::Mint>,
#[account(
mut,
token::mint = token_mint,
token::authority = store,
token::token_program = token_program,
seeds = [
constants::MARKET_VAULT_SEED,
store.key().as_ref(),
token_mint.key().as_ref(),
],
bump,
)]
pub vault: InterfaceAccount<'info, anchor_spl::token_interface::TokenAccount>,
#[account(
mut,
token::mint = token_mint,
)]
pub target: InterfaceAccount<'info, anchor_spl::token_interface::TokenAccount>,
pub token_program: Interface<'info, anchor_spl::token_interface::TokenInterface>,
}
pub(crate) fn claim_fees_from_market(ctx: Context<ClaimFeesFromMarket>) -> Result<u64> {
ctx.accounts
.store
.load()?
.validate_not_restarted()?
.validate_claim_fees_address(ctx.accounts.authority.key)?;
let event_emitter = EventEmitter::new(&ctx.accounts.event_authority, ctx.bumps.event_authority);
let amount = {
let token = ctx.accounts.token_mint.key();
let mut market = RevertibleMarket::new(&ctx.accounts.market, event_emitter)?;
let is_long_token = market
.market_meta()
.to_token_side(&token)
.map_err(CoreError::from)?;
let the_opposite_side = !is_long_token;
let is_pure = market.market_meta().is_pure();
let pool = market.claimable_fee_pool_mut().map_err(ModelError::from)?;
let mut deltas = (0, 0);
let mut amount: u64 = pool
.amount(is_long_token)
.map_err(ModelError::from)?
.min(u128::from(u64::MAX))
.try_into()
.expect("must success");
deltas.0 = (u128::from(amount))
.to_opposite_signed()
.map_err(ModelError::from)?;
if is_pure {
let the_opposite_side_amount: u64 = pool
.amount(the_opposite_side)
.map_err(ModelError::from)?
.min(u128::from(u64::MAX))
.try_into()
.expect("must success");
deltas.1 = (u128::from(the_opposite_side_amount))
.to_opposite_signed()
.map_err(ModelError::from)?;
amount = amount
.checked_add(the_opposite_side_amount)
.ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
}
if deltas.0 != 0 {
pool.apply_delta_amount(is_long_token, &deltas.0)
.map_err(ModelError::from)?;
}
if deltas.1 != 0 {
pool.apply_delta_amount(the_opposite_side, &deltas.1)
.map_err(ModelError::from)?;
}
market
.validate_market_balance_for_the_given_token(&token, amount)
.map_err(ModelError::from)?;
market.commit();
amount
};
let token = &ctx.accounts.token_mint;
MarketTransferOutOperation::builder()
.store(&ctx.accounts.store)
.market(&ctx.accounts.market)
.amount(amount)
.decimals(token.decimals)
.to(ctx.accounts.target.to_account_info())
.token_mint(token.to_account_info())
.vault(ctx.accounts.vault.to_account_info())
.token_program(ctx.accounts.token_program.to_account_info())
.event_emitter(event_emitter)
.build()
.execute()?;
msg!(
"Claimed `{}` {} from the {} market",
amount,
token.key(),
ctx.accounts.market.load()?.meta.market_token_mint
);
Ok(amount)
}