Skip to main content

percli_program/
state.rs

1use anchor_lang::prelude::*;
2use percli_core::RiskEngine;
3
4/// Market account header (v1.0 layout) — stored at the beginning of the account data.
5/// The RiskEngine state follows immediately after, accessed via raw pointer
6/// because RiskEngine is ~1.165 MB and doesn't derive Copy.
7///
8/// Layout version: v1 (introduced in percli v1.0.0). v0.9.x markets used a 136-byte
9/// header without `pending_authority` — those accounts must be migrated via
10/// `migrate_header_v1` before any other instruction can run against them.
11#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy)]
12pub struct MarketHeader {
13    /// Authority that created this market (can update params, rotate keys).
14    pub authority: Pubkey,
15    /// SPL token mint for this market's collateral.
16    pub mint: Pubkey,
17    /// Pyth price feed account for this market's oracle.
18    pub oracle: Pubkey,
19    /// Matcher authority — the only signer allowed to submit trades.
20    pub matcher: Pubkey,
21    /// Pending authority for two-step transfer. `Pubkey::default()` when no
22    /// transfer is in flight. Only the holder of this key can call
23    /// `accept_authority` to complete the handoff.
24    pub pending_authority: Pubkey,
25    /// Bump seed for the Market PDA.
26    pub bump: u8,
27    /// Bump seed for the vault token account PDA.
28    pub vault_bump: u8,
29    /// Padding for 8-byte alignment.
30    pub _padding: [u8; 6],
31}
32
33impl MarketHeader {
34    /// Current (v1) header size: 4×Pubkey + pending_authority + 2 bumps + 6 padding = 168 bytes.
35    pub const SIZE: usize = 32 + 32 + 32 + 32 + 32 + 1 + 1 + 6; // 168 bytes
36    /// Legacy (v0) header size — used only by `migrate_header_v1` to detect
37    /// pre-v1.0 accounts that still need to be expanded by 32 bytes.
38    pub const SIZE_V0: usize = 32 + 32 + 32 + 32 + 1 + 1 + 6; // 136 bytes
39}
40
41/// 8-byte discriminator for v1 market accounts: 7 fixed bytes (`b"percmrk"`)
42/// followed by a 1-byte layout version (`0x01`).
43///
44/// Note: percli v0.9 accounts use `b"percmrkt"` (last byte = `0x74` = `'t'`),
45/// the legacy v0 marker. They must be migrated via `migrate_header_v1` before
46/// any other instruction can run against them.
47pub const MARKET_DISCRIMINATOR_V1: [u8; 8] = *b"percmrk\x01";
48
49/// Returns `true` iff `data` starts with the v1 discriminator.
50#[inline]
51pub fn is_v1_market(data: &[u8]) -> bool {
52    data.len() >= 8 && data[0..8] == MARKET_DISCRIMINATOR_V1
53}
54
55/// Total account size (v1 layout): discriminator + header + engine
56pub const MARKET_ACCOUNT_SIZE: usize = 8 + MarketHeader::SIZE + std::mem::size_of::<RiskEngine>();
57
58/// Total account size for the legacy v0.9 layout. Used by `migrate_header_v1`
59/// to validate that the account being migrated is a pre-v1 market.
60pub const MARKET_ACCOUNT_SIZE_V0: usize = 8 + MarketHeader::SIZE_V0 + std::mem::size_of::<RiskEngine>();
61
62/// Market PDA signer seeds: [b"market", authority_key, &[bump]]
63pub fn market_signer_seeds<'a>(
64    authority: &'a Pubkey,
65    bump: &'a [u8; 1],
66) -> [&'a [u8]; 3] {
67    [b"market", authority.as_ref(), bump.as_ref()]
68}
69
70/// Helper to get a mutable reference to the RiskEngine from raw account data.
71///
72/// SAFETY: The caller must ensure the account data is at least
73/// `8 + MarketHeader::SIZE + size_of::<RiskEngine>()` bytes and properly
74/// initialized. The RiskEngine is #[repr(C)] with all-valid bit patterns.
75pub fn engine_from_account_data(data: &mut [u8]) -> &mut RiskEngine {
76    let offset = 8 + MarketHeader::SIZE;
77    let engine_bytes = &mut data[offset..offset + std::mem::size_of::<RiskEngine>()];
78    unsafe { &mut *(engine_bytes.as_mut_ptr() as *mut RiskEngine) }
79}
80
81pub fn engine_from_account_data_ref(data: &[u8]) -> &RiskEngine {
82    let offset = 8 + MarketHeader::SIZE;
83    let engine_bytes = &data[offset..offset + std::mem::size_of::<RiskEngine>()];
84    unsafe { &*(engine_bytes.as_ptr() as *const RiskEngine) }
85}
86
87pub fn header_from_account_data(data: &[u8]) -> std::result::Result<MarketHeader, anchor_lang::error::Error> {
88    let header_bytes = &data[8..8 + MarketHeader::SIZE];
89    MarketHeader::try_from_slice(header_bytes)
90        .map_err(|_| anchor_lang::error!(crate::error::PercolatorError::CorruptState))
91}
92
93pub fn write_header(data: &mut [u8], header: &MarketHeader) {
94    let mut cursor = &mut data[8..8 + MarketHeader::SIZE];
95    header.serialize(&mut cursor).unwrap();
96}