acta_maker_sdk/chain/
accounts.rs1use solana_sdk::{account::Account, pubkey::Pubkey};
2
3use super::ChainError;
4use crate::types::{Decimals, OrderId, PositionType, Quantity, Strike};
5
6const ACCOUNT_OFFSET_DISCRIMINATOR: usize = 0;
7const ACCOUNT_OFFSET_VERSION: usize = 1;
8const POSITION_DISCRIMINATOR: u8 = 1;
9const MARKET_DISCRIMINATOR: u8 = 4;
10
11#[repr(u8)]
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum PositionStatus {
15 None = 0,
16 Open = 1,
17 Funded = 2,
18 Liquidated = 3,
19 Settled = 4,
20}
21
22impl TryFrom<u8> for PositionStatus {
23 type Error = ChainError;
24
25 fn try_from(value: u8) -> Result<Self, Self::Error> {
26 match value {
27 0 => Ok(Self::None),
28 1 => Ok(Self::Open),
29 2 => Ok(Self::Funded),
30 3 => Ok(Self::Liquidated),
31 4 => Ok(Self::Settled),
32 other => Err(ChainError::UnknownPositionStatus(other)),
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
38pub struct PositionInfo {
39 pub position_type: PositionType,
40 pub status: PositionStatus,
41 pub taker_owner: Pubkey,
42 pub maker_owner: Pubkey,
43 pub market_pda: Pubkey,
44 pub strike: Strike,
45 pub quantity: Quantity,
46 pub order_id: OrderId,
47}
48
49#[derive(Debug, Clone)]
50pub struct MarketInfo {
51 pub underlying_mint: Pubkey,
52 pub quote_mint: Pubkey,
53 pub underlying_token_program_id: Pubkey,
54 pub quote_token_program_id: Pubkey,
55 pub underlying_decimals: Decimals,
56 pub quote_decimals: Decimals,
57}
58
59const POSITION_OFFSET_POSITION_TYPE: usize = 3;
60const POSITION_OFFSET_STATUS: usize = 4;
61const POSITION_OFFSET_TAKER_OWNER: usize = 8;
62const POSITION_OFFSET_MAKER_OWNER: usize = 40;
63const POSITION_OFFSET_MARKET: usize = 72;
64const POSITION_OFFSET_STRIKE: usize = 104;
65const POSITION_OFFSET_QUANTITY: usize = 112;
66const POSITION_OFFSET_ORDER_ID: usize = 128;
67const POSITION_MIN_LEN: usize = POSITION_OFFSET_ORDER_ID + 32;
68
69pub(crate) fn parse_position(
70 account: &Account,
71 program_id: &Pubkey,
72) -> Result<PositionInfo, ChainError> {
73 validate_account(account, program_id, POSITION_DISCRIMINATOR)?;
74 if account.data.len() < POSITION_MIN_LEN {
75 return Err(ChainError::InvalidAccountData);
76 }
77 let mut order_id = [0u8; 32];
78 order_id
79 .copy_from_slice(&account.data[POSITION_OFFSET_ORDER_ID..POSITION_OFFSET_ORDER_ID + 32]);
80 Ok(PositionInfo {
81 position_type: PositionType::try_from(account.data[POSITION_OFFSET_POSITION_TYPE])
82 .map_err(|error| ChainError::InvalidPositionType(error.0))?,
83 status: PositionStatus::try_from(account.data[POSITION_OFFSET_STATUS])?,
84 taker_owner: read_pubkey(&account.data, POSITION_OFFSET_TAKER_OWNER)?,
85 maker_owner: read_pubkey(&account.data, POSITION_OFFSET_MAKER_OWNER)?,
86 market_pda: read_pubkey(&account.data, POSITION_OFFSET_MARKET)?,
87 strike: Strike::new(read_u64(&account.data, POSITION_OFFSET_STRIKE)?),
88 quantity: Quantity::new(read_u64(&account.data, POSITION_OFFSET_QUANTITY)?),
89 order_id: OrderId::new(order_id),
90 })
91}
92
93const MARKET_OFFSET_UNDERLYING_DECIMALS: usize = 3;
94const MARKET_OFFSET_QUOTE_DECIMALS: usize = 4;
95const MARKET_OFFSET_UNDERLYING_MINT: usize = 32;
96const MARKET_OFFSET_QUOTE_MINT: usize = 64;
97const MARKET_OFFSET_UNDERLYING_TOKEN_PROGRAM: usize = 96;
98const MARKET_OFFSET_QUOTE_TOKEN_PROGRAM: usize = 128;
99const MARKET_MIN_LEN: usize = MARKET_OFFSET_QUOTE_TOKEN_PROGRAM + 32;
100
101pub(crate) fn parse_market(
102 account: &Account,
103 program_id: &Pubkey,
104) -> Result<MarketInfo, ChainError> {
105 validate_account(account, program_id, MARKET_DISCRIMINATOR)?;
106 if account.data.len() < MARKET_MIN_LEN {
107 return Err(ChainError::InvalidAccountData);
108 }
109 Ok(MarketInfo {
110 underlying_decimals: Decimals::new(account.data[MARKET_OFFSET_UNDERLYING_DECIMALS]),
111 quote_decimals: Decimals::new(account.data[MARKET_OFFSET_QUOTE_DECIMALS]),
112 underlying_mint: read_pubkey(&account.data, MARKET_OFFSET_UNDERLYING_MINT)?,
113 quote_mint: read_pubkey(&account.data, MARKET_OFFSET_QUOTE_MINT)?,
114 underlying_token_program_id: read_pubkey(
115 &account.data,
116 MARKET_OFFSET_UNDERLYING_TOKEN_PROGRAM,
117 )?,
118 quote_token_program_id: read_pubkey(&account.data, MARKET_OFFSET_QUOTE_TOKEN_PROGRAM)?,
119 })
120}
121
122fn validate_account(
123 account: &Account,
124 program_id: &Pubkey,
125 expected_discriminator: u8,
126) -> Result<(), ChainError> {
127 if account.owner != *program_id {
128 return Err(ChainError::AccountOwnerMismatch {
129 expected: *program_id,
130 actual: account.owner,
131 });
132 }
133 let discriminator = account
134 .data
135 .get(ACCOUNT_OFFSET_DISCRIMINATOR)
136 .copied()
137 .ok_or(ChainError::InvalidAccountData)?;
138 if discriminator != expected_discriminator {
139 return Err(ChainError::AccountDiscriminatorMismatch {
140 expected: expected_discriminator,
141 actual: discriminator,
142 });
143 }
144 match account.data.get(ACCOUNT_OFFSET_VERSION) {
146 None | Some(0) => return Err(ChainError::UninitializedAccount),
147 Some(_) => {}
148 }
149 Ok(())
150}
151
152fn read_pubkey(data: &[u8], offset: usize) -> Result<Pubkey, ChainError> {
153 let bytes: [u8; 32] = data
154 .get(offset..offset + 32)
155 .ok_or(ChainError::InvalidAccountData)?
156 .try_into()
157 .map_err(|_| ChainError::InvalidAccountData)?;
158 Ok(Pubkey::new_from_array(bytes))
159}
160
161fn read_u64(data: &[u8], offset: usize) -> Result<u64, ChainError> {
162 let bytes: [u8; 8] = data
163 .get(offset..offset + 8)
164 .ok_or(ChainError::InvalidAccountData)?
165 .try_into()
166 .map_err(|_| ChainError::InvalidAccountData)?;
167 Ok(u64::from_le_bytes(bytes))
168}
169
170#[cfg(test)]
171#[path = "accounts_tests.rs"]
172mod tests;