acta-maker-sdk 0.5.2

Rust SDK for Acta options market makers — WebSocket client, order signing, message types
Documentation
use super::*;

fn account(owner: Pubkey, data: Vec<u8>) -> Account {
    Account {
        lamports: 0,
        data,
        owner,
        executable: false,
        rent_epoch: 0,
    }
}

#[test]
fn position_layout_decodes_canonical_offsets() {
    let program_id = Pubkey::new_unique();
    let taker = Pubkey::new_unique();
    let maker = Pubkey::new_unique();
    let market = Pubkey::new_unique();
    let mut data = vec![0; POSITION_LEN];
    data[ACCOUNT_OFFSET_DISCRIMINATOR] = POSITION_DISCRIMINATOR;
    data[ACCOUNT_OFFSET_VERSION] = POSITION_VERSION;
    data[POSITION_OFFSET_POSITION_TYPE] = 1;
    data[POSITION_OFFSET_STATUS] = PositionStatus::Open as u8;
    data[POSITION_OFFSET_TAKER_OWNER..POSITION_OFFSET_TAKER_OWNER + 32]
        .copy_from_slice(taker.as_ref());
    data[POSITION_OFFSET_MAKER_OWNER..POSITION_OFFSET_MAKER_OWNER + 32]
        .copy_from_slice(maker.as_ref());
    data[POSITION_OFFSET_MARKET..POSITION_OFFSET_MARKET + 32].copy_from_slice(market.as_ref());
    data[POSITION_OFFSET_STRIKE..POSITION_OFFSET_STRIKE + 8].copy_from_slice(&7u64.to_le_bytes());
    data[POSITION_OFFSET_QUANTITY..POSITION_OFFSET_QUANTITY + 8]
        .copy_from_slice(&9u64.to_le_bytes());
    data[POSITION_OFFSET_ORDER_ID..POSITION_OFFSET_ORDER_ID + 32].fill(11);

    let position = PositionInfo::from_account(&account(program_id, data), &program_id).unwrap();
    assert_eq!(position.position_type, PositionType::CashSecuredPut);
    assert_eq!(position.status, PositionStatus::Open);
    assert_eq!(position.taker_owner, taker);
    assert_eq!(position.maker_owner, maker);
    assert_eq!(position.market_pda, market);
    assert_eq!(position.strike, Strike::new(7));
    assert_eq!(position.quantity, Quantity::new(9));
    assert_eq!(position.order_id, OrderId::new([11; 32]));
}

#[test]
fn account_codecs_reject_truncated_or_unknown_data() {
    let program_id = Pubkey::new_unique();
    let mut truncated = vec![0; POSITION_LEN - 1];
    truncated[ACCOUNT_OFFSET_DISCRIMINATOR] = POSITION_DISCRIMINATOR;
    truncated[ACCOUNT_OFFSET_VERSION] = POSITION_VERSION;
    assert!(matches!(
        PositionInfo::from_account(&account(program_id, truncated), &program_id),
        Err(ChainError::InvalidAccountData)
    ));
    let mut position = vec![0; POSITION_LEN];
    position[ACCOUNT_OFFSET_DISCRIMINATOR] = POSITION_DISCRIMINATOR;
    position[ACCOUNT_OFFSET_VERSION] = POSITION_VERSION;
    position[POSITION_OFFSET_STATUS] = 99;
    assert!(matches!(
        PositionInfo::from_account(&account(program_id, position), &program_id),
        Err(ChainError::UnknownPositionStatus(99))
    ));
    let mut market = vec![0; MARKET_LEN - 1];
    market[ACCOUNT_OFFSET_DISCRIMINATOR] = MARKET_DISCRIMINATOR;
    market[ACCOUNT_OFFSET_VERSION] = 1;
    assert!(matches!(
        MarketInfo::from_account(&account(program_id, market), &program_id),
        Err(ChainError::InvalidAccountData)
    ));
}

#[test]
fn account_codecs_reject_wrong_owner_type_and_uninitialized_data() {
    let program_id = Pubkey::new_unique();
    let wrong_owner = Pubkey::new_unique();
    let mut data = vec![0; POSITION_LEN];
    data[ACCOUNT_OFFSET_DISCRIMINATOR] = POSITION_DISCRIMINATOR;
    data[ACCOUNT_OFFSET_VERSION] = POSITION_VERSION;

    assert!(matches!(
        PositionInfo::from_account(&account(wrong_owner, data.clone()), &program_id),
        Err(ChainError::AccountOwnerMismatch { .. })
    ));

    data[ACCOUNT_OFFSET_DISCRIMINATOR] = MARKET_DISCRIMINATOR;
    assert!(matches!(
        PositionInfo::from_account(&account(program_id, data.clone()), &program_id),
        Err(ChainError::AccountDiscriminatorMismatch { .. })
    ));

    data[ACCOUNT_OFFSET_DISCRIMINATOR] = POSITION_DISCRIMINATOR;
    data[ACCOUNT_OFFSET_VERSION] = 0;
    assert!(matches!(
        PositionInfo::from_account(&account(program_id, data), &program_id),
        Err(ChainError::UninitializedAccount)
    ));
}

#[test]
fn market_layout_keeps_token_programs_distinct_from_oracles() {
    let program_id = Pubkey::new_unique();
    let underlying_program = Pubkey::new_unique();
    let quote_program = Pubkey::new_unique();
    let mut data = vec![0; MARKET_LEN];
    data[ACCOUNT_OFFSET_DISCRIMINATOR] = MARKET_DISCRIMINATOR;
    data[ACCOUNT_OFFSET_VERSION] = 1;
    data[MARKET_OFFSET_FLAGS] = MARKET_FLAG_PUT;
    data[MARKET_OFFSET_EXPIRY..MARKET_OFFSET_EXPIRY + 8]
        .copy_from_slice(&1_700_000_000u64.to_le_bytes());
    data[MARKET_OFFSET_UNDERLYING_DECIMALS] = 9;
    data[MARKET_OFFSET_QUOTE_DECIMALS] = 6;
    data[MARKET_OFFSET_UNDERLYING_TOKEN_PROGRAM..MARKET_OFFSET_UNDERLYING_TOKEN_PROGRAM + 32]
        .copy_from_slice(underlying_program.as_ref());
    data[MARKET_OFFSET_QUOTE_TOKEN_PROGRAM..MARKET_OFFSET_QUOTE_TOKEN_PROGRAM + 32]
        .copy_from_slice(quote_program.as_ref());

    let market = MarketInfo::from_account(&account(program_id, data.clone()), &program_id).unwrap();
    assert!(market.is_put && !market.is_finalized);
    assert_eq!(market.expiry_ts, 1_700_000_000);
    assert_eq!(market.underlying_decimals, Decimals::new(9));
    assert_eq!(market.quote_decimals, Decimals::new(6));
    assert_eq!(market.underlying_token_program_id, underlying_program);
    assert_eq!(market.quote_token_program_id, quote_program);

    let mut unknown_flag = data.clone();
    unknown_flag[MARKET_OFFSET_FLAGS] = 4;
    let mut next_version = data;
    next_version[ACCOUNT_OFFSET_VERSION] = 2;
    for data in [unknown_flag, next_version] {
        assert!(matches!(
            MarketInfo::from_account(&account(program_id, data), &program_id),
            Err(ChainError::InvalidAccountData)
        ));
    }
}

#[test]
fn maker_layout_decodes_owner_and_quote_signer_only_from_the_program() {
    let program_id = Pubkey::new_unique();
    let owner = Pubkey::new_unique();
    let signer = Pubkey::new_unique();
    let mut data = vec![0; MAKER_LEN];
    data[ACCOUNT_OFFSET_DISCRIMINATOR] = MAKER_DISCRIMINATOR;
    data[ACCOUNT_OFFSET_VERSION] = 1;
    data[MAKER_OFFSET_OWNER..MAKER_OFFSET_OWNER + 32].copy_from_slice(owner.as_ref());
    data[MAKER_OFFSET_QUOTE_SIGNER..MAKER_OFFSET_QUOTE_SIGNER + 32]
        .copy_from_slice(signer.as_ref());
    assert_eq!(
        MakerInfo::from_account(&account(program_id, data.clone()), &program_id).unwrap(),
        MakerInfo {
            owner,
            quote_signer: signer
        }
    );
    assert!(matches!(
        MakerInfo::from_account(&account(Pubkey::new_unique(), data.clone()), &program_id),
        Err(ChainError::AccountOwnerMismatch { .. })
    ));
    data.push(0);
    assert!(matches!(
        MakerInfo::from_account(&account(program_id, data), &program_id),
        Err(ChainError::InvalidAccountData)
    ));
}

#[test]
fn closed_position_tombstone_decodes_without_reopening_the_position() {
    let program_id = Pubkey::new_unique();
    let mut data = vec![0; POSITION_LEN];
    data[ACCOUNT_OFFSET_DISCRIMINATOR] = POSITION_DISCRIMINATOR;
    data[ACCOUNT_OFFSET_VERSION] = POSITION_VERSION;
    data[POSITION_OFFSET_STATUS] = 5;
    data[POSITION_OFFSET_ORDER_ID..POSITION_OFFSET_ORDER_ID + 32].fill(7);
    let position = PositionInfo::from_account(&account(program_id, data), &program_id).unwrap();
    assert_eq!(position.status, PositionStatus::Closed);
    assert_eq!(position.order_id, OrderId::new([7; 32]));
}