use solana_pubkey::Pubkey;
use crate::program::constants::{
EXCHANGE_DISCRIMINATOR, EXCHANGE_SIZE, GLOBAL_DEPOSIT_TOKEN_DISCRIMINATOR,
GLOBAL_DEPOSIT_TOKEN_SIZE, MARKET_DISCRIMINATOR, MARKET_SIZE, MAX_OUTCOMES,
ORDERBOOK_DISCRIMINATOR, ORDERBOOK_SIZE, ORDER_STATUS_DISCRIMINATOR, ORDER_STATUS_SIZE,
POSITION_DISCRIMINATOR, POSITION_SIZE, USER_NONCE_DISCRIMINATOR, USER_NONCE_SIZE,
};
use crate::program::error::{SdkError, SdkResult};
use crate::program::types::MarketStatus;
#[inline]
fn read_bytes<const N: usize>(data: &[u8], offset: usize) -> [u8; N] {
let mut arr = [0u8; N];
arr.copy_from_slice(&data[offset..offset + N]);
arr
}
#[inline]
fn read_pubkey(data: &[u8], offset: usize) -> Pubkey {
Pubkey::new_from_array(read_bytes::<32>(data, offset))
}
#[inline]
fn read_u64(data: &[u8], offset: usize) -> u64 {
u64::from_le_bytes(read_bytes::<8>(data, offset))
}
#[inline]
fn read_u32(data: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(read_bytes::<4>(data, offset))
}
#[derive(Debug, Clone)]
pub struct Exchange {
pub discriminator: [u8; 8],
pub authority: Pubkey,
pub operator: Pubkey,
pub manager: Pubkey,
pub market_count: u64,
pub paused: bool,
pub bump: u8,
pub deposit_token_count: u16,
pub fee_receiver: Pubkey,
}
impl Exchange {
pub const LEN: usize = EXCHANGE_SIZE;
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < Self::LEN {
return Err(SdkError::InvalidDataLength {
expected: Self::LEN,
actual: data.len(),
});
}
let discriminator = read_bytes::<8>(data, 0);
if discriminator != EXCHANGE_DISCRIMINATOR {
return Err(SdkError::InvalidDiscriminator {
expected: hex::encode(EXCHANGE_DISCRIMINATOR),
actual: hex::encode(discriminator),
});
}
Ok(Self {
discriminator,
authority: read_pubkey(data, 8),
operator: read_pubkey(data, 40),
manager: read_pubkey(data, 72),
market_count: read_u64(data, 104),
paused: data[112] != 0,
bump: data[113],
deposit_token_count: u16::from_le_bytes(read_bytes::<2>(data, 114)),
fee_receiver: read_pubkey(data, 116),
})
}
pub fn is_exchange_account(data: &[u8]) -> bool {
data.len() >= 8 && data[0..8] == EXCHANGE_DISCRIMINATOR
}
}
#[derive(Debug, Clone)]
pub struct Market {
pub discriminator: [u8; 8],
pub market_id: u64,
pub num_outcomes: u8,
pub status: MarketStatus,
pub bump: u8,
pub maker_fee_bps: i16,
pub taker_fee_bps: i16,
pub oracle: Pubkey,
pub question_id: [u8; 32],
pub condition_id: [u8; 32],
pub payout_numerators: [u32; MAX_OUTCOMES as usize],
pub payout_denominator: u32,
}
impl Market {
pub const LEN: usize = MARKET_SIZE;
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < Self::LEN {
return Err(SdkError::InvalidDataLength {
expected: Self::LEN,
actual: data.len(),
});
}
let discriminator = read_bytes::<8>(data, 0);
if discriminator != MARKET_DISCRIMINATOR {
return Err(SdkError::InvalidDiscriminator {
expected: hex::encode(MARKET_DISCRIMINATOR),
actual: hex::encode(discriminator),
});
}
let mut payout_numerators = [0u32; MAX_OUTCOMES as usize];
for (index, numerator) in payout_numerators.iter_mut().enumerate() {
*numerator = read_u32(data, 120 + (index * 4));
}
Ok(Self {
discriminator,
market_id: read_u64(data, 8),
num_outcomes: data[16],
status: MarketStatus::try_from(data[17])?,
bump: data[18],
maker_fee_bps: i16::from_le_bytes(read_bytes::<2>(data, 20)),
taker_fee_bps: i16::from_le_bytes(read_bytes::<2>(data, 22)),
oracle: read_pubkey(data, 24),
question_id: read_bytes::<32>(data, 56),
condition_id: read_bytes::<32>(data, 88),
payout_numerators,
payout_denominator: read_u32(data, 144),
})
}
pub fn is_market_account(data: &[u8]) -> bool {
data.len() >= 8 && data[0..8] == MARKET_DISCRIMINATOR
}
}
#[derive(Debug, Clone)]
pub struct Position {
pub discriminator: [u8; 8],
pub owner: Pubkey,
pub market: Pubkey,
pub bump: u8,
}
impl Position {
pub const LEN: usize = POSITION_SIZE;
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < Self::LEN {
return Err(SdkError::InvalidDataLength {
expected: Self::LEN,
actual: data.len(),
});
}
let discriminator = read_bytes::<8>(data, 0);
if discriminator != POSITION_DISCRIMINATOR {
return Err(SdkError::InvalidDiscriminator {
expected: hex::encode(POSITION_DISCRIMINATOR),
actual: hex::encode(discriminator),
});
}
Ok(Self {
discriminator,
owner: read_pubkey(data, 8),
market: read_pubkey(data, 40),
bump: data[72],
})
}
pub fn is_position_account(data: &[u8]) -> bool {
data.len() >= 8 && data[0..8] == POSITION_DISCRIMINATOR
}
}
#[derive(Debug, Clone)]
pub struct OrderStatus {
pub discriminator: [u8; 8],
pub remaining: u64,
pub base_remaining: u64,
pub is_cancelled: bool,
}
impl OrderStatus {
pub const LEN: usize = ORDER_STATUS_SIZE;
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < Self::LEN {
return Err(SdkError::InvalidDataLength {
expected: Self::LEN,
actual: data.len(),
});
}
let discriminator = read_bytes::<8>(data, 0);
if discriminator != ORDER_STATUS_DISCRIMINATOR {
return Err(SdkError::InvalidDiscriminator {
expected: hex::encode(ORDER_STATUS_DISCRIMINATOR),
actual: hex::encode(discriminator),
});
}
Ok(Self {
discriminator,
remaining: read_u64(data, 8),
base_remaining: read_u64(data, 16),
is_cancelled: data[24] != 0,
})
}
pub fn is_order_status_account(data: &[u8]) -> bool {
data.len() >= 8 && data[0..8] == ORDER_STATUS_DISCRIMINATOR
}
}
#[derive(Debug, Clone)]
pub struct UserNonce {
pub discriminator: [u8; 8],
pub nonce: u64,
}
impl UserNonce {
pub const LEN: usize = USER_NONCE_SIZE;
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < Self::LEN {
return Err(SdkError::InvalidDataLength {
expected: Self::LEN,
actual: data.len(),
});
}
let discriminator = read_bytes::<8>(data, 0);
if discriminator != USER_NONCE_DISCRIMINATOR {
return Err(SdkError::InvalidDiscriminator {
expected: hex::encode(USER_NONCE_DISCRIMINATOR),
actual: hex::encode(discriminator),
});
}
Ok(Self {
discriminator,
nonce: read_u64(data, 8),
})
}
pub fn is_user_nonce_account(data: &[u8]) -> bool {
data.len() >= 8 && data[0..8] == USER_NONCE_DISCRIMINATOR
}
}
#[derive(Debug, Clone)]
pub struct Orderbook {
pub discriminator: [u8; 8],
pub market: Pubkey,
pub mint_a: Pubkey,
pub mint_b: Pubkey,
pub lookup_table: Pubkey,
pub base_index: u8,
pub bump: u8,
}
impl Orderbook {
pub const LEN: usize = ORDERBOOK_SIZE;
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < Self::LEN {
return Err(SdkError::InvalidDataLength {
expected: Self::LEN,
actual: data.len(),
});
}
let discriminator = read_bytes::<8>(data, 0);
if discriminator != ORDERBOOK_DISCRIMINATOR {
return Err(SdkError::InvalidDiscriminator {
expected: hex::encode(ORDERBOOK_DISCRIMINATOR),
actual: hex::encode(discriminator),
});
}
Ok(Self {
discriminator,
market: read_pubkey(data, 8),
mint_a: read_pubkey(data, 40),
mint_b: read_pubkey(data, 72),
lookup_table: read_pubkey(data, 104),
base_index: data[136],
bump: data[137],
})
}
pub fn is_orderbook_account(data: &[u8]) -> bool {
data.len() >= 8 && data[0..8] == ORDERBOOK_DISCRIMINATOR
}
}
#[derive(Debug, Clone)]
pub struct GlobalDepositToken {
pub discriminator: [u8; 8],
pub mint: Pubkey,
pub active: bool,
pub bump: u8,
pub index: u16,
}
impl GlobalDepositToken {
pub const LEN: usize = GLOBAL_DEPOSIT_TOKEN_SIZE;
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < Self::LEN {
return Err(SdkError::InvalidDataLength {
expected: Self::LEN,
actual: data.len(),
});
}
let discriminator = read_bytes::<8>(data, 0);
if discriminator != GLOBAL_DEPOSIT_TOKEN_DISCRIMINATOR {
return Err(SdkError::InvalidDiscriminator {
expected: hex::encode(GLOBAL_DEPOSIT_TOKEN_DISCRIMINATOR),
actual: hex::encode(discriminator),
});
}
Ok(Self {
discriminator,
mint: read_pubkey(data, 8),
active: data[40] != 0,
bump: data[41],
index: u16::from_le_bytes(read_bytes::<2>(data, 42)),
})
}
pub fn is_global_deposit_token_account(data: &[u8]) -> bool {
data.len() >= 8 && data[0..8] == GLOBAL_DEPOSIT_TOKEN_DISCRIMINATOR
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exchange_deserialization() {
let mut data = vec![0u8; EXCHANGE_SIZE];
data[0..8].copy_from_slice(&EXCHANGE_DISCRIMINATOR);
data[8..40].copy_from_slice(&[1u8; 32]);
data[40..72].copy_from_slice(&[2u8; 32]);
data[72..104].copy_from_slice(&[3u8; 32]);
data[104..112].copy_from_slice(&5u64.to_le_bytes());
data[112] = 0;
data[113] = 255;
data[114..116].copy_from_slice(&7u16.to_le_bytes());
data[116..148].copy_from_slice(&[4u8; 32]);
let exchange = Exchange::deserialize(&data).unwrap();
assert_eq!(exchange.manager, Pubkey::new_from_array([3u8; 32]));
assert_eq!(exchange.market_count, 5);
assert!(!exchange.paused);
assert_eq!(exchange.bump, 255);
assert_eq!(exchange.deposit_token_count, 7);
assert_eq!(exchange.fee_receiver, Pubkey::new_from_array([4u8; 32]));
}
#[test]
fn test_market_deserialization() {
let mut data = vec![0u8; MARKET_SIZE];
data[0..8].copy_from_slice(&MARKET_DISCRIMINATOR);
data[8..16].copy_from_slice(&42u64.to_le_bytes());
data[16] = 3;
data[17] = 1; data[18] = 254;
data[20..22].copy_from_slice(&(-10i16).to_le_bytes());
data[22..24].copy_from_slice(&25i16.to_le_bytes());
data[120..124].copy_from_slice(&7u32.to_le_bytes());
data[124..128].copy_from_slice(&3u32.to_le_bytes());
data[144..148].copy_from_slice(&10u32.to_le_bytes());
let market = Market::deserialize(&data).unwrap();
assert_eq!(market.market_id, 42);
assert_eq!(market.num_outcomes, 3);
assert_eq!(market.status, MarketStatus::Active);
assert_eq!(market.bump, 254);
assert_eq!(market.maker_fee_bps, -10);
assert_eq!(market.taker_fee_bps, 25);
assert_eq!(market.payout_numerators[0], 7);
assert_eq!(market.payout_numerators[1], 3);
assert_eq!(market.payout_denominator, 10);
}
#[test]
fn test_position_deserialization() {
let mut data = vec![0u8; POSITION_SIZE];
data[0..8].copy_from_slice(&POSITION_DISCRIMINATOR);
data[8..40].copy_from_slice(&[1u8; 32]);
data[40..72].copy_from_slice(&[2u8; 32]);
data[72] = 253;
let position = Position::deserialize(&data).unwrap();
assert_eq!(position.bump, 253);
}
#[test]
fn test_order_status_deserialization() {
let mut data = vec![0u8; ORDER_STATUS_SIZE];
data[0..8].copy_from_slice(&ORDER_STATUS_DISCRIMINATOR);
data[8..16].copy_from_slice(&1000u64.to_le_bytes());
data[16..24].copy_from_slice(&750u64.to_le_bytes());
data[24] = 0;
let order_status = OrderStatus::deserialize(&data).unwrap();
assert_eq!(order_status.remaining, 1000);
assert_eq!(order_status.base_remaining, 750);
assert!(!order_status.is_cancelled);
}
#[test]
fn test_user_nonce_deserialization() {
let mut data = vec![0u8; USER_NONCE_SIZE];
data[0..8].copy_from_slice(&USER_NONCE_DISCRIMINATOR);
data[8..16].copy_from_slice(&99u64.to_le_bytes());
let user_nonce = UserNonce::deserialize(&data).unwrap();
assert_eq!(user_nonce.nonce, 99);
}
#[test]
fn test_orderbook_deserialization() {
let mut data = vec![0u8; ORDERBOOK_SIZE];
data[0..8].copy_from_slice(&ORDERBOOK_DISCRIMINATOR);
data[8..40].copy_from_slice(&[1u8; 32]);
data[40..72].copy_from_slice(&[2u8; 32]);
data[72..104].copy_from_slice(&[3u8; 32]);
data[104..136].copy_from_slice(&[4u8; 32]);
data[136] = 1;
data[137] = 252;
let orderbook = Orderbook::deserialize(&data).unwrap();
assert_eq!(orderbook.market, Pubkey::new_from_array([1u8; 32]));
assert_eq!(orderbook.mint_a, Pubkey::new_from_array([2u8; 32]));
assert_eq!(orderbook.mint_b, Pubkey::new_from_array([3u8; 32]));
assert_eq!(orderbook.lookup_table, Pubkey::new_from_array([4u8; 32]));
assert_eq!(orderbook.base_index, 1);
assert_eq!(orderbook.bump, 252);
}
#[test]
fn test_orderbook_is_orderbook_account() {
let mut data = vec![0u8; ORDERBOOK_SIZE];
data[0..8].copy_from_slice(&ORDERBOOK_DISCRIMINATOR);
assert!(Orderbook::is_orderbook_account(&data));
let bad_data = vec![0u8; ORDERBOOK_SIZE];
assert!(!Orderbook::is_orderbook_account(&bad_data));
}
#[test]
fn test_global_deposit_token_deserialization() {
let mut data = vec![0u8; GLOBAL_DEPOSIT_TOKEN_SIZE];
data[0..8].copy_from_slice(&GLOBAL_DEPOSIT_TOKEN_DISCRIMINATOR);
data[8..40].copy_from_slice(&[5u8; 32]);
data[40] = 1;
data[41] = 251;
let gdt = GlobalDepositToken::deserialize(&data).unwrap();
assert_eq!(gdt.mint, Pubkey::new_from_array([5u8; 32]));
assert!(gdt.active);
assert_eq!(gdt.bump, 251);
assert_eq!(gdt.index, 0);
}
#[test]
fn test_global_deposit_token_inactive() {
let mut data = vec![0u8; GLOBAL_DEPOSIT_TOKEN_SIZE];
data[0..8].copy_from_slice(&GLOBAL_DEPOSIT_TOKEN_DISCRIMINATOR);
data[40] = 0;
let gdt = GlobalDepositToken::deserialize(&data).unwrap();
assert!(!gdt.active);
}
#[test]
fn test_global_deposit_token_is_account() {
let mut data = vec![0u8; GLOBAL_DEPOSIT_TOKEN_SIZE];
data[0..8].copy_from_slice(&GLOBAL_DEPOSIT_TOKEN_DISCRIMINATOR);
assert!(GlobalDepositToken::is_global_deposit_token_account(&data));
let bad_data = vec![0u8; GLOBAL_DEPOSIT_TOKEN_SIZE];
assert!(!GlobalDepositToken::is_global_deposit_token_account(
&bad_data
));
}
}