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: 7 fixed bytes + 1 layout-version byte (`0x01` = v1).
69    // The version byte at offset [7] is what `migrate_header_v1` reads to
70    // distinguish v0 (`0x74` = `'t'`) from v1 (`0x01`) accounts.
71    data[0..7].copy_from_slice(b"percmrk");
72    data[7] = 0x01;
73
74    let header = MarketHeader {
75        authority: ctx.accounts.authority.key(),
76        mint: ctx.accounts.mint.key(),
77        oracle: ctx.accounts.oracle.key(),
78        matcher: ctx.accounts.matcher.key(),
79        pending_authority: Pubkey::default(),
80        bump: ctx.bumps.market,
81        vault_bump: ctx.bumps.vault,
82        _padding: [0; 6],
83    };
84    write_header(&mut data, &header);
85
86    require!(init_oracle_price > 0, PercolatorError::InvalidOraclePriceValue);
87
88    let engine = engine_from_account_data(&mut data);
89    let risk_params = params.to_risk_params();
90    engine.init_in_place(risk_params, init_slot, init_oracle_price);
91
92    emit!(events::MarketInitialized {
93        authority: ctx.accounts.authority.key(),
94        mint: ctx.accounts.mint.key(),
95        oracle: ctx.accounts.oracle.key(),
96        matcher: ctx.accounts.matcher.key(),
97        init_slot,
98        init_oracle_price,
99    });
100
101    Ok(())
102}
103
104/// Anchor-friendly input for RiskParams (all primitives, no wrapper types).
105#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
106pub struct RiskParamsInput {
107    pub warmup_period_slots: u64,
108    pub maintenance_margin_bps: u64,
109    pub initial_margin_bps: u64,
110    pub trading_fee_bps: u64,
111    pub max_accounts: u64,
112    pub new_account_fee: u64,
113    pub maintenance_fee_per_slot: u64,
114    pub max_crank_staleness_slots: u64,
115    pub liquidation_fee_bps: u64,
116    pub liquidation_fee_cap: u64,
117    pub min_liquidation_abs: u64,
118    pub min_initial_deposit: u64,
119    pub min_nonzero_mm_req: u64,
120    pub min_nonzero_im_req: u64,
121    pub insurance_floor: u64,
122}
123
124impl RiskParamsInput {
125    pub fn to_risk_params(&self) -> RiskParams {
126        use percli_core::U128;
127        RiskParams {
128            warmup_period_slots: self.warmup_period_slots,
129            maintenance_margin_bps: self.maintenance_margin_bps,
130            initial_margin_bps: self.initial_margin_bps,
131            trading_fee_bps: self.trading_fee_bps,
132            max_accounts: self.max_accounts,
133            new_account_fee: U128::new(self.new_account_fee as u128),
134            maintenance_fee_per_slot: U128::new(self.maintenance_fee_per_slot as u128),
135            max_crank_staleness_slots: self.max_crank_staleness_slots,
136            liquidation_fee_bps: self.liquidation_fee_bps,
137            liquidation_fee_cap: U128::new(self.liquidation_fee_cap as u128),
138            min_liquidation_abs: U128::new(self.min_liquidation_abs as u128),
139            min_initial_deposit: U128::new(self.min_initial_deposit as u128),
140            min_nonzero_mm_req: self.min_nonzero_mm_req as u128,
141            min_nonzero_im_req: self.min_nonzero_im_req as u128,
142            insurance_floor: U128::new(self.insurance_floor as u128),
143        }
144    }
145}