Skip to main content

percli_program/instructions/
initialize_market.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, Token, TokenAccount};
3use percli_core::RiskParams;
4
5use crate::error::PercolatorError;
6use crate::instructions::events;
7#[allow(unused_imports)]
8use crate::state::{engine_from_account_data, write_header, MarketHeader, MARKET_ACCOUNT_SIZE};
9
10#[derive(Accounts)]
11pub struct InitializeMarket<'info> {
12    #[account(mut)]
13    pub authority: Signer<'info>,
14
15    /// CHECK: Pre-created by the client via `system_instruction::create_account`
16    /// with size = MARKET_ACCOUNT_SIZE and owner = this program.
17    /// Large accounts (>10 KB) cannot be created via CPI due to the realloc limit,
18    /// so the client must create the account before calling initialize_market.
19    #[account(
20        mut,
21        owner = crate::ID @ PercolatorError::AccountNotFound,
22        constraint = market.data_len() >= MARKET_ACCOUNT_SIZE @ PercolatorError::AccountNotFound,
23        seeds = [b"market", authority.key().as_ref()],
24        bump,
25    )]
26    pub market: UncheckedAccount<'info>,
27
28    /// The SPL token mint for this market's collateral (e.g. USDC).
29    pub mint: Account<'info, Mint>,
30
31    /// Vault token account — holds all deposited collateral for this market.
32    #[account(
33        init,
34        payer = authority,
35        token::mint = mint,
36        token::authority = market,
37        seeds = [b"vault", market.key().as_ref()],
38        bump,
39    )]
40    pub vault: Account<'info, TokenAccount>,
41
42    /// CHECK: Pyth price feed for this market. Stored in header; validated on crank.
43    pub oracle: UncheckedAccount<'info>,
44
45    /// Matcher authority — the only signer allowed to submit trades.
46    /// CHECK: Stored in header. Can be any pubkey (e.g. a program or multisig).
47    pub matcher: UncheckedAccount<'info>,
48
49    pub token_program: Program<'info, Token>,
50    pub system_program: Program<'info, System>,
51}
52
53pub fn handler(
54    ctx: Context<InitializeMarket>,
55    init_slot: u64,
56    init_oracle_price: u64,
57    params: RiskParamsInput,
58) -> Result<()> {
59    let market = &ctx.accounts.market;
60    let mut data = market.try_borrow_mut_data()?;
61
62    // Ensure the account hasn't already been initialized
63    require!(
64        data[0..8] == [0u8; 8],
65        PercolatorError::AccountNotFound
66    );
67
68    // Write discriminator (first 8 bytes) — use a fixed marker
69    data[0..8].copy_from_slice(b"percmrkt");
70
71    let header = MarketHeader {
72        authority: ctx.accounts.authority.key(),
73        mint: ctx.accounts.mint.key(),
74        oracle: ctx.accounts.oracle.key(),
75        matcher: ctx.accounts.matcher.key(),
76        bump: ctx.bumps.market,
77        vault_bump: ctx.bumps.vault,
78        _padding: [0; 6],
79    };
80    write_header(&mut data, &header);
81
82    require!(init_oracle_price > 0, PercolatorError::InvalidOraclePriceValue);
83
84    let engine = engine_from_account_data(&mut data);
85    let risk_params = params.to_risk_params();
86    engine.init_in_place(risk_params, init_slot, init_oracle_price);
87
88    emit!(events::MarketInitialized {
89        authority: ctx.accounts.authority.key(),
90        mint: ctx.accounts.mint.key(),
91        oracle: ctx.accounts.oracle.key(),
92        matcher: ctx.accounts.matcher.key(),
93        init_slot,
94        init_oracle_price,
95    });
96
97    Ok(())
98}
99
100/// Anchor-friendly input for RiskParams (all primitives, no wrapper types).
101#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
102pub struct RiskParamsInput {
103    pub warmup_period_slots: u64,
104    pub maintenance_margin_bps: u64,
105    pub initial_margin_bps: u64,
106    pub trading_fee_bps: u64,
107    pub max_accounts: u64,
108    pub new_account_fee: u64,
109    pub maintenance_fee_per_slot: u64,
110    pub max_crank_staleness_slots: u64,
111    pub liquidation_fee_bps: u64,
112    pub liquidation_fee_cap: u64,
113    pub min_liquidation_abs: u64,
114    pub min_initial_deposit: u64,
115    pub min_nonzero_mm_req: u64,
116    pub min_nonzero_im_req: u64,
117    pub insurance_floor: u64,
118}
119
120impl RiskParamsInput {
121    pub fn to_risk_params(&self) -> RiskParams {
122        use percli_core::U128;
123        RiskParams {
124            warmup_period_slots: self.warmup_period_slots,
125            maintenance_margin_bps: self.maintenance_margin_bps,
126            initial_margin_bps: self.initial_margin_bps,
127            trading_fee_bps: self.trading_fee_bps,
128            max_accounts: self.max_accounts,
129            new_account_fee: U128::new(self.new_account_fee as u128),
130            maintenance_fee_per_slot: U128::new(self.maintenance_fee_per_slot as u128),
131            max_crank_staleness_slots: self.max_crank_staleness_slots,
132            liquidation_fee_bps: self.liquidation_fee_bps,
133            liquidation_fee_cap: U128::new(self.liquidation_fee_cap as u128),
134            min_liquidation_abs: U128::new(self.min_liquidation_abs as u128),
135            min_initial_deposit: U128::new(self.min_initial_deposit as u128),
136            min_nonzero_mm_req: self.min_nonzero_mm_req as u128,
137            min_nonzero_im_req: self.min_nonzero_im_req as u128,
138            insurance_floor: U128::new(self.insurance_floor as u128),
139        }
140    }
141}