use solana_instruction::{AccountMeta, Instruction};
use solana_pubkey::Pubkey;
fn system_program_id() -> Pubkey {
solana_system_interface::program::ID
}
use crate::program::constants::{
instruction, ALT_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, MAX_MAKERS, MAX_OUTCOMES,
MIN_OUTCOMES, MPL_TOKEN_METADATA_PROGRAM_ID, RENT_SYSVAR_ID, TOKEN_PROGRAM_ID,
};
use crate::program::error::{SdkError, SdkResult};
use crate::program::orders::OrderPayload;
use crate::program::pda::{
get_alt_pda, get_condition_tombstone_pda, get_conditional_mint_pda, get_exchange_pda,
get_global_deposit_token_pda, get_market_pda, get_mint_authority_pda, get_mpl_metadata_pda,
get_order_status_pda, get_orderbook_pda, get_position_alt_pda, get_position_pda,
get_user_global_deposit_pda, get_user_nonce_pda, get_vault_pda,
};
use crate::program::types::{
ActivateMarketParams, AddDepositMintParams, BuildDepositParams, BuildMergeParams,
CloseOrderStatusParams, CloseOrderbookAltParams, CloseOrderbookParams, ClosePositionAltParams,
ClosePositionTokenAccountsParams, ConditionalMetadataParams, CreateMarketParams,
CreateOrderbookParams, DepositAndSwapParams, DepositToGlobalAltContext, DepositToGlobalParams,
ExtendPositionTokensParams, GlobalToMarketDepositParams, InitPositionTokensParams,
MatchOrdersMultiParams, RedeemWinningsParams, SetAuthorityParams, SetFeeReceiverParams,
SetManagerParams, SetMarketFeesParams, SettleMarketParams, WhitelistDepositTokenParams,
WithdrawFromGlobalParams, WithdrawFromPositionParams,
};
use crate::program::utils::{
get_conditional_token_ata, get_deposit_token_ata, serialize_conditional_metadata,
validate_fee_pair, validate_outcome_count,
};
use crate::program::{derive_condition_id, ORDER_SIZE, SIGNATURE_SIZE};
const MATCH_ORDER_HEADER_SIZE: usize = ORDER_SIZE + SIGNATURE_SIZE + 2;
const DEPOSIT_AND_SWAP_HEADER_SIZE: usize = ORDER_SIZE + SIGNATURE_SIZE + 3;
const MAKER_MATCH_SIZE: usize = ORDER_SIZE + SIGNATURE_SIZE + 16;
fn signer_mut(pubkey: Pubkey) -> AccountMeta {
AccountMeta::new(pubkey, true)
}
fn signer(pubkey: Pubkey) -> AccountMeta {
AccountMeta::new_readonly(pubkey, true)
}
fn writable(pubkey: Pubkey) -> AccountMeta {
AccountMeta::new(pubkey, false)
}
fn readonly(pubkey: Pubkey) -> AccountMeta {
AccountMeta::new_readonly(pubkey, false)
}
fn zero_pubkey() -> Pubkey {
Pubkey::new_from_array([0u8; 32])
}
struct OrderbookMintInput {
mint: Pubkey,
deposit_mint: Pubkey,
outcome_index: u8,
is_base: bool,
}
struct CanonicalOrderbookMints {
mint_a: OrderbookMintInput,
mint_b: OrderbookMintInput,
}
impl CanonicalOrderbookMints {
fn from_params(params: &CreateOrderbookParams) -> SdkResult<Self> {
if params.base_index > 1 {
return Err(SdkError::InvalidOutcomeIndex {
index: params.base_index,
max: 1,
});
}
if params.mint_a == params.mint_b {
return Err(SdkError::InvalidMintOrder);
}
let left = OrderbookMintInput {
mint: params.mint_a,
deposit_mint: params.mint_a_deposit_mint,
outcome_index: params.mint_a_outcome_index,
is_base: params.base_index == 0,
};
let right = OrderbookMintInput {
mint: params.mint_b,
deposit_mint: params.mint_b_deposit_mint,
outcome_index: params.mint_b_outcome_index,
is_base: params.base_index == 1,
};
let (mint_a, mint_b) = if left.mint.as_ref() < right.mint.as_ref() {
(left, right)
} else {
(right, left)
};
Ok(Self { mint_a, mint_b })
}
fn base_index(&self) -> u8 {
if self.mint_a.is_base {
0
} else {
1
}
}
}
pub fn build_initialize_ix(authority: &Pubkey, program_id: &Pubkey) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![
signer_mut(*authority),
writable(exchange),
readonly(system_program_id()),
];
let data = vec![instruction::INITIALIZE];
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_create_market_ix(
params: &CreateMarketParams,
market_id: u64,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
validate_outcome_count(params.num_outcomes)?;
validate_fee_pair(params.maker_fee_bps, params.taker_fee_bps)?;
let (exchange, _) = get_exchange_pda(program_id);
let (market, _) = get_market_pda(market_id, program_id);
let condition_id =
derive_condition_id(¶ms.oracle, ¶ms.question_id, params.num_outcomes);
let (condition_tombstone, _) = get_condition_tombstone_pda(&condition_id, program_id);
let keys = vec![
signer_mut(params.manager),
writable(exchange),
writable(market),
readonly(system_program_id()),
writable(condition_tombstone),
];
let mut data = Vec::with_capacity(70);
data.push(instruction::CREATE_MARKET);
data.push(params.num_outcomes);
data.extend_from_slice(params.oracle.as_ref());
data.extend_from_slice(¶ms.question_id);
data.extend_from_slice(¶ms.maker_fee_bps.to_le_bytes());
data.extend_from_slice(¶ms.taker_fee_bps.to_le_bytes());
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_add_deposit_mint_ix(
params: &AddDepositMintParams,
market: &Pubkey,
num_outcomes: u8,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
validate_outcome_count(num_outcomes)?;
let (exchange, _) = get_exchange_pda(program_id);
let (vault, _) = get_vault_pda(¶ms.deposit_mint, market, program_id);
let (mint_authority, _) = get_mint_authority_pda(market, program_id);
let (global_deposit_token, _) = get_global_deposit_token_pda(¶ms.deposit_mint, program_id);
let mut keys = vec![
signer_mut(params.manager),
readonly(exchange),
readonly(*market),
readonly(params.deposit_mint),
writable(vault),
readonly(mint_authority),
readonly(TOKEN_PROGRAM_ID),
readonly(system_program_id()),
readonly(global_deposit_token),
];
for i in 0..num_outcomes {
let (mint, _) = get_conditional_mint_pda(market, ¶ms.deposit_mint, i, program_id);
keys.push(writable(mint));
}
let data = vec![instruction::ADD_DEPOSIT_MINT];
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_deposit_ix(
params: &BuildDepositParams,
num_outcomes: u8,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (vault, _) = get_vault_pda(¶ms.deposit_mint, ¶ms.market, program_id);
let (mint_authority, _) = get_mint_authority_pda(¶ms.market, program_id);
let (position, _) = get_position_pda(¶ms.user, ¶ms.market, program_id);
let user_deposit_ata = get_deposit_token_ata(¶ms.user, ¶ms.deposit_mint);
let mut keys = vec![
signer_mut(params.user),
readonly(exchange),
readonly(params.market),
readonly(params.deposit_mint),
writable(vault),
writable(user_deposit_ata),
writable(position),
readonly(mint_authority),
readonly(TOKEN_PROGRAM_ID),
readonly(ASSOCIATED_TOKEN_PROGRAM_ID),
readonly(system_program_id()),
];
for i in 0..num_outcomes {
let (mint, _) =
get_conditional_mint_pda(¶ms.market, ¶ms.deposit_mint, i, program_id);
keys.push(writable(mint));
let position_ata = get_conditional_token_ata(&position, &mint);
keys.push(writable(position_ata));
}
let mut data = Vec::with_capacity(9);
data.push(instruction::MINT_COMPLETE_SET);
data.extend_from_slice(¶ms.amount.to_le_bytes());
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_merge_ix(
params: &BuildMergeParams,
num_outcomes: u8,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (vault, _) = get_vault_pda(¶ms.deposit_mint, ¶ms.market, program_id);
let (mint_authority, _) = get_mint_authority_pda(¶ms.market, program_id);
let (position, _) = get_position_pda(¶ms.user, ¶ms.market, program_id);
let user_deposit_ata = get_deposit_token_ata(¶ms.user, ¶ms.deposit_mint);
let mut keys = vec![
signer_mut(params.user),
readonly(exchange),
readonly(params.market),
readonly(params.deposit_mint),
writable(vault),
writable(position),
writable(user_deposit_ata),
readonly(mint_authority),
readonly(TOKEN_PROGRAM_ID),
];
for i in 0..num_outcomes {
let (mint, _) =
get_conditional_mint_pda(¶ms.market, ¶ms.deposit_mint, i, program_id);
keys.push(writable(mint));
let position_ata = get_conditional_token_ata(&position, &mint);
keys.push(writable(position_ata));
}
let mut data = Vec::with_capacity(9);
data.push(instruction::MERGE_COMPLETE_SET);
data.extend_from_slice(¶ms.amount.to_le_bytes());
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_cancel_order_ix(
operator: &Pubkey,
market: &Pubkey,
order: &OrderPayload,
program_id: &Pubkey,
) -> Instruction {
let order_hash = order.hash();
let (exchange, _) = get_exchange_pda(program_id);
let (order_status, _) = get_order_status_pda(&order_hash, program_id);
let keys = vec![
signer_mut(*operator),
readonly(exchange),
readonly(*market),
writable(order_status),
];
let mut data = Vec::with_capacity(266);
data.push(instruction::CANCEL_ORDER);
data.extend_from_slice(&order_hash);
data.extend_from_slice(&order.serialize());
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_increment_nonce_ix(user: &Pubkey, program_id: &Pubkey) -> Instruction {
let (user_nonce, _) = get_user_nonce_pda(user, program_id);
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![
signer_mut(*user),
writable(user_nonce),
readonly(system_program_id()),
readonly(exchange),
];
let data = vec![instruction::INCREMENT_NONCE];
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_settle_market_ix(
params: &SettleMarketParams,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
validate_payout_numerators(¶ms.payout_numerators)?;
let (exchange, _) = get_exchange_pda(program_id);
let (market, _) = get_market_pda(params.market_id, program_id);
let keys = vec![signer(params.oracle), readonly(exchange), writable(market)];
let mut data = Vec::with_capacity(1 + (params.payout_numerators.len() * 4));
data.push(instruction::SETTLE_MARKET);
for numerator in ¶ms.payout_numerators {
data.extend_from_slice(&numerator.to_le_bytes());
}
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
fn validate_payout_numerators(payout_numerators: &[u32]) -> SdkResult<()> {
let count = payout_numerators.len();
if count < MIN_OUTCOMES as usize || count > MAX_OUTCOMES as usize {
return Err(SdkError::InvalidOutcomeCount {
count: u8::try_from(count).unwrap_or(u8::MAX),
});
}
let mut denominator = 0u32;
for numerator in payout_numerators {
denominator = denominator
.checked_add(*numerator)
.ok_or(SdkError::Overflow)?;
}
if denominator == 0 {
return Err(SdkError::InvalidPayoutNumerators);
}
Ok(())
}
pub fn build_redeem_winnings_ix(
params: &RedeemWinningsParams,
outcome_index: u8,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (vault, _) = get_vault_pda(¶ms.deposit_mint, ¶ms.market, program_id);
let (mint_authority, _) = get_mint_authority_pda(¶ms.market, program_id);
let (position, _) = get_position_pda(¶ms.user, ¶ms.market, program_id);
let (conditional_mint, _) = get_conditional_mint_pda(
¶ms.market,
¶ms.deposit_mint,
outcome_index,
program_id,
);
let position_conditional_ata = get_conditional_token_ata(&position, &conditional_mint);
let user_deposit_ata = get_deposit_token_ata(¶ms.user, ¶ms.deposit_mint);
let keys = vec![
signer_mut(params.user),
readonly(params.market),
readonly(params.deposit_mint),
writable(vault),
writable(conditional_mint),
readonly(position),
writable(position_conditional_ata),
writable(user_deposit_ata),
readonly(mint_authority),
readonly(TOKEN_PROGRAM_ID),
readonly(exchange),
];
let mut data = Vec::with_capacity(10);
data.push(instruction::REDEEM_WINNINGS);
data.extend_from_slice(¶ms.amount.to_le_bytes());
data.push(outcome_index);
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_set_paused_ix(authority: &Pubkey, paused: bool, program_id: &Pubkey) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![signer_mut(*authority), writable(exchange)];
let data = vec![instruction::SET_PAUSED, if paused { 1 } else { 0 }];
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_set_operator_ix(
authority: &Pubkey,
new_operator: &Pubkey,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![signer_mut(*authority), writable(exchange)];
let mut data = Vec::with_capacity(33);
data.push(instruction::SET_OPERATOR);
data.extend_from_slice(new_operator.as_ref());
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_withdraw_from_position_ix(
params: &WithdrawFromPositionParams,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (position, _) = get_position_pda(¶ms.user, ¶ms.market, program_id);
let position_ata = get_conditional_token_ata(&position, ¶ms.mint);
let user_ata = get_conditional_token_ata(¶ms.user, ¶ms.mint);
let keys = vec![
signer_mut(params.user),
readonly(params.market),
writable(position),
readonly(params.mint),
writable(position_ata),
writable(user_ata),
readonly(TOKEN_PROGRAM_ID),
readonly(exchange),
];
let mut data = Vec::with_capacity(10);
data.push(instruction::WITHDRAW_FROM_POSITION);
data.extend_from_slice(¶ms.amount.to_le_bytes());
data.push(params.outcome_index);
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_activate_market_ix(params: &ActivateMarketParams, program_id: &Pubkey) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (market, _) = get_market_pda(params.market_id, program_id);
let keys = vec![
signer_mut(params.manager),
readonly(exchange),
writable(market),
];
let data = vec![instruction::ACTIVATE_MARKET];
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_match_orders_multi_ix(
params: &MatchOrdersMultiParams,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
if params.maker_orders.is_empty() {
return Err(SdkError::MissingField("maker_orders".to_string()));
}
if params.maker_orders.len() > MAX_MAKERS {
return Err(SdkError::TooManyMakers {
count: params.maker_orders.len(),
});
}
if params.maker_orders.len() != params.maker_fill_amounts.len() {
return Err(SdkError::MissingField("maker_fill_amounts".to_string()));
}
if params.maker_orders.len() != params.taker_fill_amounts.len() {
return Err(SdkError::MissingField("taker_fill_amounts".to_string()));
}
let (exchange, _) = get_exchange_pda(program_id);
let (orderbook, _) = get_orderbook_pda(¶ms.base_mint, ¶ms.quote_mint, program_id);
let taker_order_hash = params.taker_order.hash();
let (taker_nonce, _) = get_user_nonce_pda(¶ms.taker_order.maker, program_id);
let (taker_position, _) =
get_position_pda(¶ms.taker_order.maker, ¶ms.market, program_id);
let taker_base_ata = get_conditional_token_ata(&taker_position, ¶ms.base_mint);
let taker_quote_ata = get_conditional_token_ata(&taker_position, ¶ms.quote_mint);
let fee_receiver_quote_ata =
get_conditional_token_ata(¶ms.fee_receiver, ¶ms.quote_mint);
let taker_full_fill = (params.full_fill_bitmask >> 7) & 1 == 1;
let mut keys = Vec::new();
keys.push(signer_mut(params.operator));
keys.push(readonly(exchange));
keys.push(readonly(params.market));
keys.push(readonly(orderbook));
if !taker_full_fill {
let (taker_order_status, _) = get_order_status_pda(&taker_order_hash, program_id);
keys.push(writable(taker_order_status));
}
keys.push(readonly(taker_nonce));
keys.push(writable(taker_position));
keys.push(readonly(params.base_mint));
keys.push(readonly(params.quote_mint));
keys.push(writable(taker_base_ata));
keys.push(writable(taker_quote_ata));
keys.push(readonly(TOKEN_PROGRAM_ID));
keys.push(readonly(system_program_id()));
keys.push(writable(fee_receiver_quote_ata));
for (i, maker_order) in params.maker_orders.iter().enumerate() {
let maker_full_fill = (params.full_fill_bitmask >> i) & 1 == 1;
if !maker_full_fill {
let maker_order_hash = maker_order.hash();
let (maker_order_status, _) = get_order_status_pda(&maker_order_hash, program_id);
keys.push(writable(maker_order_status));
}
let (maker_nonce, _) = get_user_nonce_pda(&maker_order.maker, program_id);
let (maker_position, _) = get_position_pda(&maker_order.maker, ¶ms.market, program_id);
let maker_base_ata = get_conditional_token_ata(&maker_position, ¶ms.base_mint);
let maker_quote_ata = get_conditional_token_ata(&maker_position, ¶ms.quote_mint);
keys.push(readonly(maker_nonce));
keys.push(writable(maker_position));
keys.push(writable(maker_base_ata));
keys.push(writable(maker_quote_ata));
}
let taker_compact = params.taker_order.to_order();
let num_makers = params.maker_orders.len() as u8;
let data_size = 1 + MATCH_ORDER_HEADER_SIZE + (params.maker_orders.len() * MAKER_MATCH_SIZE);
let mut data = Vec::with_capacity(data_size);
data.push(instruction::MATCH_ORDERS_MULTI);
data.extend_from_slice(&taker_compact.serialize());
data.extend_from_slice(¶ms.taker_order.signature);
data.push(num_makers);
data.push(params.full_fill_bitmask);
for (i, maker_order) in params.maker_orders.iter().enumerate() {
let maker_compact = maker_order.to_order();
data.extend_from_slice(&maker_compact.serialize());
data.extend_from_slice(&maker_order.signature);
data.extend_from_slice(¶ms.maker_fill_amounts[i].to_le_bytes());
data.extend_from_slice(¶ms.taker_fill_amounts[i].to_le_bytes());
}
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_create_orderbook_ix(
params: &CreateOrderbookParams,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
let canonical = CanonicalOrderbookMints::from_params(params)?;
let (exchange, _) = get_exchange_pda(program_id);
let (orderbook, _) =
get_orderbook_pda(&canonical.mint_a.mint, &canonical.mint_b.mint, program_id);
let (lookup_table, _) = get_alt_pda(&orderbook, params.recent_slot);
let quote_mint = if canonical.base_index() == 0 {
canonical.mint_b.mint
} else {
canonical.mint_a.mint
};
let fee_receiver_quote_ata = get_conditional_token_ata(¶ms.fee_receiver, "e_mint);
let keys = vec![
signer_mut(params.manager),
readonly(params.market),
readonly(canonical.mint_a.mint),
readonly(canonical.mint_b.mint),
writable(orderbook),
writable(lookup_table),
readonly(exchange),
readonly(*ALT_PROGRAM_ID),
readonly(system_program_id()),
readonly(canonical.mint_a.deposit_mint),
readonly(canonical.mint_b.deposit_mint),
readonly(TOKEN_PROGRAM_ID),
readonly(ASSOCIATED_TOKEN_PROGRAM_ID),
readonly(params.fee_receiver),
writable(fee_receiver_quote_ata),
];
let mut data = Vec::with_capacity(12);
data.push(instruction::CREATE_ORDERBOOK);
data.extend_from_slice(¶ms.recent_slot.to_le_bytes());
data.push(canonical.base_index());
data.push(canonical.mint_a.outcome_index);
data.push(canonical.mint_b.outcome_index);
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_set_authority_ix(params: &SetAuthorityParams, program_id: &Pubkey) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![signer_mut(params.current_authority), writable(exchange)];
let mut data = Vec::with_capacity(33);
data.push(instruction::SET_AUTHORITY);
data.extend_from_slice(params.new_authority.as_ref());
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_set_manager_ix(params: &SetManagerParams, program_id: &Pubkey) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![signer_mut(params.authority), writable(exchange)];
let mut data = Vec::with_capacity(33);
data.push(instruction::SET_MANAGER);
data.extend_from_slice(params.new_manager.as_ref());
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_set_market_fees_ix(
params: &SetMarketFeesParams,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
if params.updates.is_empty() {
return Err(SdkError::MissingField("updates".to_string()));
}
let (exchange, _) = get_exchange_pda(program_id);
let mut keys = Vec::with_capacity(2 + params.updates.len());
keys.push(signer_mut(params.manager));
keys.push(readonly(exchange));
let mut data = Vec::with_capacity(1 + params.updates.len() * 4);
data.push(instruction::SET_MARKET_FEES);
for update in ¶ms.updates {
validate_fee_pair(update.maker_fee_bps, update.taker_fee_bps)?;
keys.push(writable(update.market));
data.extend_from_slice(&update.maker_fee_bps.to_le_bytes());
data.extend_from_slice(&update.taker_fee_bps.to_le_bytes());
}
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_set_fee_receiver_ix(
params: &SetFeeReceiverParams,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
if params.new_fee_receiver == zero_pubkey() {
return Err(SdkError::InvalidFeeReceiver);
}
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![signer_mut(params.authority), writable(exchange)];
let mut data = Vec::with_capacity(33);
data.push(instruction::SET_FEE_RECEIVER);
data.extend_from_slice(params.new_fee_receiver.as_ref());
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_create_conditional_metadata_ix(
params: &ConditionalMetadataParams,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
build_conditional_metadata_ix(params, true, program_id)
}
pub fn build_update_conditional_metadata_ix(
params: &ConditionalMetadataParams,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
build_conditional_metadata_ix(params, false, program_id)
}
fn build_conditional_metadata_ix(
params: &ConditionalMetadataParams,
is_create: bool,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
if params.outcome_index >= MAX_OUTCOMES {
return Err(SdkError::InvalidOutcomeIndex {
index: params.outcome_index,
max: MAX_OUTCOMES - 1,
});
}
let (exchange, _) = get_exchange_pda(program_id);
let (conditional_mint, _) = get_conditional_mint_pda(
¶ms.market,
¶ms.deposit_mint,
params.outcome_index,
program_id,
);
let (mint_authority, _) = get_mint_authority_pda(¶ms.market, program_id);
let (metadata, _) = get_mpl_metadata_pda(&conditional_mint);
let mut data =
Vec::with_capacity(2 + 12 + params.name.len() + params.symbol.len() + params.uri.len());
data.push(if is_create {
instruction::CREATE_CONDITIONAL_METADATA
} else {
instruction::UPDATE_CONDITIONAL_METADATA
});
data.push(params.outcome_index);
data.extend(serialize_conditional_metadata(
¶ms.name,
¶ms.symbol,
¶ms.uri,
)?);
let mut keys = vec![
if is_create {
signer_mut(params.manager)
} else {
signer(params.manager)
},
readonly(exchange),
readonly(params.market),
readonly(params.deposit_mint),
readonly(conditional_mint),
writable(metadata),
readonly(mint_authority),
readonly(*MPL_TOKEN_METADATA_PROGRAM_ID),
];
if is_create {
keys.push(readonly(system_program_id()));
keys.push(readonly(RENT_SYSVAR_ID));
}
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_whitelist_deposit_token_ix(
params: &WhitelistDepositTokenParams,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (global_deposit_token, _) = get_global_deposit_token_pda(¶ms.mint, program_id);
let keys = vec![
signer_mut(params.authority),
writable(exchange),
readonly(params.mint),
writable(global_deposit_token),
readonly(system_program_id()),
];
let data = vec![instruction::WHITELIST_DEPOSIT_TOKEN];
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_deposit_to_global_ix(
params: &DepositToGlobalParams,
program_id: &Pubkey,
) -> Instruction {
build_deposit_to_global_ix_inner(params, None, program_id)
}
pub fn build_deposit_to_global_ix_with_alt(
params: &DepositToGlobalParams,
alt_context: DepositToGlobalAltContext,
program_id: &Pubkey,
) -> Instruction {
build_deposit_to_global_ix_inner(params, Some(alt_context), program_id)
}
fn build_deposit_to_global_ix_inner(
params: &DepositToGlobalParams,
alt_context: Option<DepositToGlobalAltContext>,
program_id: &Pubkey,
) -> Instruction {
let (global_deposit_token, _) = get_global_deposit_token_pda(¶ms.mint, program_id);
let (user_global_deposit, _) =
get_user_global_deposit_pda(¶ms.user, ¶ms.mint, program_id);
let (exchange, _) = get_exchange_pda(program_id);
let user_token_account = get_deposit_token_ata(¶ms.user, ¶ms.mint);
let mut keys = vec![
signer_mut(params.user),
readonly(global_deposit_token),
readonly(params.mint),
writable(user_global_deposit),
writable(user_token_account),
readonly(TOKEN_PROGRAM_ID),
readonly(system_program_id()),
readonly(exchange),
];
let mut data = Vec::with_capacity(9);
data.push(instruction::DEPOSIT_TO_GLOBAL);
data.extend_from_slice(¶ms.amount.to_le_bytes());
if let Some(alt_context) = alt_context {
let (user_nonce, _) = get_user_nonce_pda(¶ms.user, program_id);
let lookup_table = match alt_context {
DepositToGlobalAltContext::Create { recent_slot } => {
data.extend_from_slice(&recent_slot.to_le_bytes());
get_alt_pda(&user_nonce, recent_slot).0
}
DepositToGlobalAltContext::Extend { lookup_table } => lookup_table,
};
keys.push(readonly(user_nonce));
keys.push(writable(lookup_table));
keys.push(readonly(*ALT_PROGRAM_ID));
}
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_global_to_market_deposit_ix(
params: &GlobalToMarketDepositParams,
num_outcomes: u8,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (vault, _) = get_vault_pda(¶ms.deposit_mint, ¶ms.market, program_id);
let (global_deposit_token, _) = get_global_deposit_token_pda(¶ms.deposit_mint, program_id);
let (user_global_deposit, _) =
get_user_global_deposit_pda(¶ms.user, ¶ms.deposit_mint, program_id);
let (position, _) = get_position_pda(¶ms.user, ¶ms.market, program_id);
let (mint_authority, _) = get_mint_authority_pda(¶ms.market, program_id);
let mut keys = vec![
signer_mut(params.user),
readonly(exchange),
readonly(params.market),
readonly(params.deposit_mint),
writable(vault),
readonly(global_deposit_token),
writable(user_global_deposit),
writable(position),
readonly(mint_authority),
readonly(TOKEN_PROGRAM_ID),
readonly(ASSOCIATED_TOKEN_PROGRAM_ID),
readonly(system_program_id()),
];
for i in 0..num_outcomes {
let (mint, _) =
get_conditional_mint_pda(¶ms.market, ¶ms.deposit_mint, i, program_id);
keys.push(writable(mint));
let position_ata = get_conditional_token_ata(&position, &mint);
keys.push(writable(position_ata));
}
let mut data = Vec::with_capacity(9);
data.push(instruction::GLOBAL_TO_MARKET_DEPOSIT);
data.extend_from_slice(¶ms.amount.to_le_bytes());
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_init_position_tokens_ix(
params: &InitPositionTokensParams,
num_outcomes: u8,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (position, _) = get_position_pda(¶ms.user, ¶ms.market, program_id);
let (lookup_table, _) = get_position_alt_pda(&position, params.recent_slot);
let (mint_authority, _) = get_mint_authority_pda(¶ms.market, program_id);
let mut keys = vec![
signer_mut(params.payer),
readonly(params.user),
readonly(exchange),
readonly(params.market),
writable(position),
writable(lookup_table),
readonly(mint_authority),
readonly(TOKEN_PROGRAM_ID),
readonly(ASSOCIATED_TOKEN_PROGRAM_ID),
readonly(*ALT_PROGRAM_ID),
readonly(system_program_id()),
];
for deposit_mint in ¶ms.deposit_mints {
let (vault, _) = get_vault_pda(deposit_mint, ¶ms.market, program_id);
let (gdt, _) = get_global_deposit_token_pda(deposit_mint, program_id);
keys.push(readonly(*deposit_mint));
keys.push(readonly(vault));
keys.push(readonly(gdt));
for i in 0..num_outcomes {
let (mint, _) = get_conditional_mint_pda(¶ms.market, deposit_mint, i, program_id);
keys.push(readonly(mint));
let position_ata = get_conditional_token_ata(&position, &mint);
keys.push(writable(position_ata));
}
}
let mut data = Vec::with_capacity(10);
data.push(instruction::INIT_POSITION_TOKENS);
data.extend_from_slice(¶ms.recent_slot.to_le_bytes());
data.push(params.deposit_mints.len() as u8);
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_deposit_and_swap_ix(
params: &DepositAndSwapParams,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
if params.makers.is_empty() {
return Err(SdkError::MissingField("makers".to_string()));
}
if params.makers.len() > MAX_MAKERS {
return Err(SdkError::TooManyMakers {
count: params.makers.len(),
});
}
let (exchange, _) = get_exchange_pda(program_id);
let (orderbook, _) = get_orderbook_pda(¶ms.base_mint, ¶ms.quote_mint, program_id);
let (mint_authority, _) = get_mint_authority_pda(¶ms.market, program_id);
let (taker_position, _) =
get_position_pda(¶ms.taker_order.maker, ¶ms.market, program_id);
let (taker_nonce, _) = get_user_nonce_pda(¶ms.taker_order.maker, program_id);
let fee_receiver_quote_ata =
get_conditional_token_ata(¶ms.fee_receiver, ¶ms.quote_mint);
let taker_side = params.taker_order.side as u8;
let (receive_mint, give_mint) = if taker_side == 0 {
(¶ms.base_mint, ¶ms.quote_mint)
} else {
(¶ms.quote_mint, ¶ms.base_mint)
};
let mut full_fill_bitmask: u8 = 0;
let mut deposit_bitmask: u8 = 0;
if params.taker_is_full_fill {
full_fill_bitmask |= 0x80;
}
if params.taker_is_deposit {
deposit_bitmask |= 0x80;
}
for (i, maker) in params.makers.iter().enumerate() {
if maker.is_full_fill {
full_fill_bitmask |= 1 << i;
}
if maker.is_deposit {
deposit_bitmask |= 1 << i;
}
}
let mut keys = Vec::new();
keys.push(signer_mut(params.operator));
keys.push(readonly(exchange));
keys.push(readonly(params.market));
keys.push(readonly(orderbook));
keys.push(readonly(mint_authority));
keys.push(readonly(TOKEN_PROGRAM_ID));
keys.push(writable(fee_receiver_quote_ata));
if !params.taker_is_full_fill {
let taker_order_hash = params.taker_order.hash();
let (taker_order_status, _) = get_order_status_pda(&taker_order_hash, program_id);
keys.push(writable(taker_order_status));
}
let taker_receive_ata = get_conditional_token_ata(&taker_position, receive_mint);
let taker_give_ata = get_conditional_token_ata(&taker_position, give_mint);
keys.push(readonly(taker_nonce));
keys.push(writable(taker_position));
keys.push(readonly(params.base_mint));
keys.push(readonly(params.quote_mint));
keys.push(writable(taker_receive_ata));
keys.push(writable(taker_give_ata));
keys.push(readonly(system_program_id()));
if params.taker_is_deposit {
let dm = ¶ms.taker_deposit_mint;
let (vault, _) = get_vault_pda(dm, ¶ms.market, program_id);
let (gdt, _) = get_global_deposit_token_pda(dm, program_id);
let (taker_global_deposit, _) =
get_user_global_deposit_pda(¶ms.taker_order.maker, dm, program_id);
keys.push(readonly(*dm));
keys.push(writable(vault));
keys.push(readonly(gdt));
keys.push(writable(taker_global_deposit));
for i in 0..params.num_outcomes {
let (cond_mint, _) = get_conditional_mint_pda(¶ms.market, dm, i, program_id);
let ata = get_conditional_token_ata(&taker_position, &cond_mint);
keys.push(writable(cond_mint));
keys.push(writable(ata));
}
}
for maker in ¶ms.makers {
let (maker_nonce, _) = get_user_nonce_pda(&maker.order.maker, program_id);
let (maker_position, _) = get_position_pda(&maker.order.maker, ¶ms.market, program_id);
if !maker.is_full_fill {
let maker_order_hash = maker.order.hash();
let (maker_order_status, _) = get_order_status_pda(&maker_order_hash, program_id);
keys.push(writable(maker_order_status));
}
keys.push(readonly(maker_nonce));
keys.push(writable(maker_position));
if maker.is_deposit {
let dm = &maker.deposit_mint;
let (vault, _) = get_vault_pda(dm, ¶ms.market, program_id);
let (gdt, _) = get_global_deposit_token_pda(dm, program_id);
let (maker_global_deposit, _) =
get_user_global_deposit_pda(&maker.order.maker, dm, program_id);
keys.push(readonly(*dm));
keys.push(writable(vault));
keys.push(readonly(gdt));
keys.push(writable(maker_global_deposit));
for j in 0..params.num_outcomes {
let (cond_mint, _) = get_conditional_mint_pda(¶ms.market, dm, j, program_id);
let maker_ata = get_conditional_token_ata(&maker_position, &cond_mint);
keys.push(writable(cond_mint));
keys.push(writable(maker_ata));
}
}
let maker_receive_ata = get_conditional_token_ata(&maker_position, receive_mint);
let maker_give_ata = get_conditional_token_ata(&maker_position, give_mint);
keys.push(writable(maker_receive_ata));
keys.push(writable(maker_give_ata));
}
let taker_compact = params.taker_order.to_order();
let num_makers = params.makers.len() as u8;
let data_size = 1 + DEPOSIT_AND_SWAP_HEADER_SIZE + (params.makers.len() * MAKER_MATCH_SIZE);
let mut data = Vec::with_capacity(data_size);
data.push(instruction::DEPOSIT_AND_SWAP);
data.extend_from_slice(&taker_compact.serialize());
data.extend_from_slice(¶ms.taker_order.signature);
data.push(num_makers);
data.push(full_fill_bitmask);
data.push(deposit_bitmask);
for maker in ¶ms.makers {
let maker_compact = maker.order.to_order();
data.extend_from_slice(&maker_compact.serialize());
data.extend_from_slice(&maker.order.signature);
data.extend_from_slice(&maker.maker_fill_amount.to_le_bytes());
data.extend_from_slice(&maker.taker_fill_amount.to_le_bytes());
}
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_extend_position_tokens_ix(
params: &ExtendPositionTokensParams,
num_outcomes: u8,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
if params.deposit_mints.is_empty() {
return Err(SdkError::MissingField("deposit_mints".to_string()));
}
let (exchange, _) = get_exchange_pda(program_id);
let (position, _) = get_position_pda(¶ms.user, ¶ms.market, program_id);
let mut keys = vec![
signer_mut(params.operator),
readonly(params.user),
readonly(exchange),
readonly(params.market),
readonly(position),
writable(params.lookup_table),
readonly(TOKEN_PROGRAM_ID),
readonly(ASSOCIATED_TOKEN_PROGRAM_ID),
readonly(*ALT_PROGRAM_ID),
readonly(system_program_id()),
];
for deposit_mint in ¶ms.deposit_mints {
let (vault, _) = get_vault_pda(deposit_mint, ¶ms.market, program_id);
let (global_deposit_token, _) = get_global_deposit_token_pda(deposit_mint, program_id);
keys.push(readonly(*deposit_mint));
keys.push(readonly(vault));
keys.push(readonly(global_deposit_token));
for i in 0..num_outcomes {
let (cond_mint, _) =
get_conditional_mint_pda(¶ms.market, deposit_mint, i, program_id);
let position_ata = get_conditional_token_ata(&position, &cond_mint);
keys.push(readonly(cond_mint));
keys.push(writable(position_ata));
}
}
let data = vec![
instruction::EXTEND_POSITION_TOKENS,
params.deposit_mints.len() as u8,
];
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data,
})
}
pub fn build_withdraw_from_global_ix(
params: &WithdrawFromGlobalParams,
program_id: &Pubkey,
) -> Instruction {
let (global_deposit_token, _) = get_global_deposit_token_pda(¶ms.mint, program_id);
let (user_global_deposit, _) =
get_user_global_deposit_pda(¶ms.user, ¶ms.mint, program_id);
let (exchange, _) = get_exchange_pda(program_id);
let user_token_account = get_deposit_token_ata(¶ms.user, ¶ms.mint);
let keys = vec![
signer_mut(params.user),
readonly(global_deposit_token),
readonly(params.mint),
writable(user_global_deposit),
writable(user_token_account),
readonly(TOKEN_PROGRAM_ID),
readonly(exchange),
];
let mut data = vec![instruction::WITHDRAW_FROM_GLOBAL];
data.extend_from_slice(¶ms.amount.to_le_bytes());
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_close_position_alt_ix(
params: &ClosePositionAltParams,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![
signer_mut(params.operator),
readonly(exchange),
readonly(params.position),
readonly(params.market),
writable(params.lookup_table),
readonly(*ALT_PROGRAM_ID),
];
Instruction {
program_id: *program_id,
accounts: keys,
data: vec![instruction::CLOSE_POSITION_ALT],
}
}
pub fn build_close_order_status_ix(
params: &CloseOrderStatusParams,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let (order_status, _) = get_order_status_pda(¶ms.order_hash, program_id);
let keys = vec![
signer_mut(params.operator),
readonly(exchange),
writable(order_status),
];
let mut data = Vec::with_capacity(33);
data.push(instruction::CLOSE_ORDER_STATUS);
data.extend_from_slice(¶ms.order_hash);
Instruction {
program_id: *program_id,
accounts: keys,
data,
}
}
pub fn build_close_position_token_accounts_ix(
params: &ClosePositionTokenAccountsParams,
num_outcomes: u8,
program_id: &Pubkey,
) -> SdkResult<Instruction> {
validate_outcome_count(num_outcomes)?;
if params.deposit_mints.is_empty() {
return Err(SdkError::MissingField("deposit_mints".to_string()));
}
let (exchange, _) = get_exchange_pda(program_id);
let mut keys = vec![
signer_mut(params.operator),
readonly(exchange),
readonly(params.market),
readonly(params.position),
readonly(TOKEN_PROGRAM_ID),
];
for deposit_mint in ¶ms.deposit_mints {
keys.push(readonly(*deposit_mint));
for i in 0..num_outcomes {
let (conditional_mint, _) =
get_conditional_mint_pda(¶ms.market, deposit_mint, i, program_id);
keys.push(readonly(conditional_mint));
let position_ata = get_conditional_token_ata(¶ms.position, &conditional_mint);
keys.push(writable(position_ata));
}
}
Ok(Instruction {
program_id: *program_id,
accounts: keys,
data: vec![instruction::CLOSE_POSITION_TOKEN_ACCOUNTS],
})
}
pub fn build_close_orderbook_alt_ix(
params: &CloseOrderbookAltParams,
program_id: &Pubkey,
) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![
signer_mut(params.operator),
readonly(exchange),
readonly(params.orderbook),
readonly(params.market),
writable(params.lookup_table),
readonly(*ALT_PROGRAM_ID),
];
Instruction {
program_id: *program_id,
accounts: keys,
data: vec![instruction::CLOSE_ORDERBOOK_ALT],
}
}
pub fn build_close_orderbook_ix(params: &CloseOrderbookParams, program_id: &Pubkey) -> Instruction {
let (exchange, _) = get_exchange_pda(program_id);
let keys = vec![
signer_mut(params.operator),
readonly(exchange),
writable(params.orderbook),
readonly(params.market),
readonly(params.lookup_table),
];
Instruction {
program_id: *program_id,
accounts: keys,
data: vec![instruction::CLOSE_ORDERBOOK],
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::env::LightconeEnv;
use crate::program::types::{
scalar_to_payout_numerators, MakerFill, MarketFeeUpdate, OrderSide, ScalarResolutionParams,
};
fn test_program_id() -> Pubkey {
LightconeEnv::default().program_id()
}
#[test]
fn test_build_initialize_ix() {
let authority = Pubkey::new_unique();
let program_id = test_program_id();
let ix = build_initialize_ix(&authority, &program_id);
assert_eq!(ix.program_id, program_id);
assert_eq!(ix.accounts.len(), 3);
assert_eq!(ix.data, vec![instruction::INITIALIZE]);
}
#[test]
fn test_build_increment_nonce_ix() {
let user = Pubkey::new_unique();
let program_id = test_program_id();
let ix = build_increment_nonce_ix(&user, &program_id);
assert_eq!(ix.program_id, program_id);
assert_eq!(ix.accounts.len(), 4);
assert_eq!(ix.data, vec![instruction::INCREMENT_NONCE]);
}
#[test]
fn test_build_set_paused_ix() {
let authority = Pubkey::new_unique();
let program_id = test_program_id();
let ix_pause = build_set_paused_ix(&authority, true, &program_id);
assert_eq!(ix_pause.data, vec![instruction::SET_PAUSED, 1]);
let ix_unpause = build_set_paused_ix(&authority, false, &program_id);
assert_eq!(ix_unpause.data, vec![instruction::SET_PAUSED, 0]);
}
#[test]
fn test_build_set_operator_ix() {
let authority = Pubkey::new_unique();
let new_operator = Pubkey::new_unique();
let program_id = test_program_id();
let ix = build_set_operator_ix(&authority, &new_operator, &program_id);
assert_eq!(ix.data.len(), 33);
assert_eq!(ix.data[0], instruction::SET_OPERATOR);
assert_eq!(&ix.data[1..33], new_operator.as_ref());
}
#[test]
fn test_build_create_market_ix() {
let params = CreateMarketParams {
manager: Pubkey::new_unique(),
num_outcomes: 3,
oracle: Pubkey::new_unique(),
question_id: [42u8; 32],
maker_fee_bps: 10,
taker_fee_bps: 20,
};
let program_id = test_program_id();
let ix = build_create_market_ix(¶ms, 0, &program_id).unwrap();
assert_eq!(ix.accounts.len(), 5);
assert_eq!(ix.data.len(), 70); assert_eq!(ix.data[0], instruction::CREATE_MARKET);
assert_eq!(ix.data[1], 3);
assert_eq!(&ix.data[66..68], &10i16.to_le_bytes());
assert_eq!(&ix.data[68..70], &20i16.to_le_bytes());
}
#[test]
fn test_build_create_market_invalid_outcomes() {
let params = CreateMarketParams {
manager: Pubkey::new_unique(),
num_outcomes: 7, oracle: Pubkey::new_unique(),
question_id: [0u8; 32],
maker_fee_bps: 0,
taker_fee_bps: 0,
};
let program_id = test_program_id();
let result = build_create_market_ix(¶ms, 0, &program_id);
assert!(result.is_err());
}
#[test]
fn test_build_add_deposit_mint_ix() {
let program_id = test_program_id();
let market = Pubkey::new_unique();
let params = AddDepositMintParams {
manager: Pubkey::new_unique(),
deposit_mint: Pubkey::new_unique(),
};
let ix = build_add_deposit_mint_ix(¶ms, &market, 2, &program_id).unwrap();
assert_eq!(ix.accounts.len(), 11);
assert!(!ix.accounts[2].is_writable);
assert_eq!(ix.data, vec![instruction::ADD_DEPOSIT_MINT]);
}
#[test]
fn test_build_activate_market_ix() {
let params = ActivateMarketParams {
manager: Pubkey::new_unique(),
market_id: 5,
};
let program_id = test_program_id();
let ix = build_activate_market_ix(¶ms, &program_id);
assert_eq!(ix.accounts.len(), 3);
assert_eq!(ix.data, vec![instruction::ACTIVATE_MARKET]);
}
#[test]
fn test_build_settle_market_ix() {
let params = SettleMarketParams {
oracle: Pubkey::new_unique(),
market_id: 1,
payout_numerators: vec![7, 3],
};
let program_id = test_program_id();
let ix = build_settle_market_ix(¶ms, &program_id).unwrap();
assert_eq!(ix.accounts.len(), 3);
assert!(ix.accounts[0].is_signer);
assert!(!ix.accounts[0].is_writable);
assert_eq!(ix.data.len(), 9);
assert_eq!(ix.data[0], instruction::SETTLE_MARKET);
assert_eq!(&ix.data[1..5], &7u32.to_le_bytes());
assert_eq!(&ix.data[5..9], &3u32.to_le_bytes());
}
#[test]
fn test_build_settle_market_rejects_invalid_vectors() {
let program_id = test_program_id();
let oracle = Pubkey::new_unique();
for payout_numerators in [vec![], vec![0, 0], vec![1], vec![1; 7]] {
let params = SettleMarketParams {
oracle,
market_id: 1,
payout_numerators,
};
assert!(build_settle_market_ix(¶ms, &program_id).is_err());
}
}
#[test]
fn test_winner_takes_all_payout_numerators() {
let params = SettleMarketParams::winner_takes_all(Pubkey::new_unique(), 1, 2, 4).unwrap();
assert_eq!(params.payout_numerators, vec![0, 0, 1, 0]);
}
#[test]
fn test_scalar_to_payout_numerators() {
let params = ScalarResolutionParams {
min_value: 0,
max_value: 100,
resolved_value: 25,
lower_outcome_index: 0,
upper_outcome_index: 1,
num_outcomes: 2,
};
assert_eq!(scalar_to_payout_numerators(params).unwrap(), vec![3, 1]);
let clamped_low = ScalarResolutionParams {
resolved_value: -5,
..params
};
assert_eq!(
scalar_to_payout_numerators(clamped_low).unwrap(),
vec![1, 0]
);
let clamped_high = ScalarResolutionParams {
resolved_value: 120,
..params
};
assert_eq!(
scalar_to_payout_numerators(clamped_high).unwrap(),
vec![0, 1]
);
}
#[test]
fn test_signed_scalar_to_payout_numerators_reduces() {
let params = ScalarResolutionParams {
min_value: -10_000,
max_value: 40_000,
resolved_value: 15_250,
lower_outcome_index: 0,
upper_outcome_index: 1,
num_outcomes: 2,
};
assert_eq!(scalar_to_payout_numerators(params).unwrap(), vec![99, 101]);
}
#[test]
fn test_build_redeem_winnings_ix_includes_outcome_and_exchange() {
let program_id = test_program_id();
let params = RedeemWinningsParams {
user: Pubkey::new_unique(),
market: Pubkey::new_unique(),
deposit_mint: Pubkey::new_unique(),
amount: 1_000,
};
let (exchange, _) = get_exchange_pda(&program_id);
let ix = build_redeem_winnings_ix(¶ms, 1, &program_id);
assert_eq!(ix.accounts.len(), 11);
assert_eq!(ix.accounts[10].pubkey, exchange);
assert!(!ix.accounts[5].is_writable);
assert_eq!(ix.data.len(), 10);
assert_eq!(ix.data[0], instruction::REDEEM_WINNINGS);
assert_eq!(&ix.data[1..9], &1_000u64.to_le_bytes());
assert_eq!(ix.data[9], 1);
}
#[test]
fn test_build_cancel_order_ix() {
let maker = Pubkey::new_unique();
let market = Pubkey::new_unique();
let program_id = test_program_id();
let order = OrderPayload {
nonce: 1,
salt: 0,
maker,
market,
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [0u8; 64],
};
let operator = Pubkey::new_unique();
let ix = build_cancel_order_ix(&operator, &market, &order, &program_id);
assert_eq!(ix.accounts.len(), 4);
assert_eq!(ix.data.len(), 266); assert_eq!(ix.data[0], instruction::CANCEL_ORDER);
}
#[test]
fn test_build_withdraw_from_position_ix() {
let program_id = test_program_id();
let params = WithdrawFromPositionParams {
user: Pubkey::new_unique(),
market: Pubkey::new_unique(),
mint: Pubkey::new_unique(),
amount: 1000,
outcome_index: 0,
};
let ix = build_withdraw_from_position_ix(¶ms, &program_id);
assert_eq!(ix.accounts.len(), 8);
assert_eq!(ix.data.len(), 10); assert_eq!(ix.data[0], instruction::WITHDRAW_FROM_POSITION);
assert_eq!(ix.data[9], 0); }
#[test]
fn test_build_create_orderbook_ix() {
let program_id = test_program_id();
let params = CreateOrderbookParams {
manager: Pubkey::new_unique(),
market: Pubkey::new_unique(),
mint_a: Pubkey::new_from_array([2u8; 32]),
mint_b: Pubkey::new_from_array([1u8; 32]),
fee_receiver: Pubkey::new_unique(),
mint_a_deposit_mint: Pubkey::new_from_array([12u8; 32]),
mint_b_deposit_mint: Pubkey::new_from_array([11u8; 32]),
recent_slot: 12345,
base_index: 0,
mint_a_outcome_index: 2,
mint_b_outcome_index: 1,
};
let ix = build_create_orderbook_ix(¶ms, &program_id).unwrap();
assert_eq!(ix.accounts.len(), 15);
assert_eq!(ix.data.len(), 12); assert_eq!(ix.data[0], instruction::CREATE_ORDERBOOK);
assert_eq!(ix.accounts[2].pubkey, params.mint_b);
assert_eq!(ix.accounts[3].pubkey, params.mint_a);
assert_eq!(ix.accounts[13].pubkey, params.fee_receiver);
assert_eq!(ix.data[9], 1); assert_eq!(ix.data[10], 1); assert_eq!(ix.data[11], 2); }
#[test]
fn test_build_set_authority_ix() {
let program_id = test_program_id();
let params = SetAuthorityParams {
current_authority: Pubkey::new_unique(),
new_authority: Pubkey::new_unique(),
};
let ix = build_set_authority_ix(¶ms, &program_id);
assert_eq!(ix.accounts.len(), 2);
assert_eq!(ix.data.len(), 33); assert_eq!(ix.data[0], instruction::SET_AUTHORITY);
assert_eq!(&ix.data[1..33], params.new_authority.as_ref());
}
#[test]
fn test_build_set_manager_ix() {
let program_id = test_program_id();
let params = SetManagerParams {
authority: Pubkey::new_unique(),
new_manager: Pubkey::new_unique(),
};
let ix = build_set_manager_ix(¶ms, &program_id);
assert_eq!(ix.accounts.len(), 2);
assert_eq!(ix.data.len(), 33);
assert_eq!(ix.data[0], instruction::SET_MANAGER);
assert_eq!(&ix.data[1..33], params.new_manager.as_ref());
}
#[test]
fn test_build_set_market_fees_ix() {
let program_id = test_program_id();
let market = Pubkey::new_unique();
let params = SetMarketFeesParams {
manager: Pubkey::new_unique(),
updates: vec![MarketFeeUpdate {
market,
maker_fee_bps: -10,
taker_fee_bps: 25,
}],
};
let ix = build_set_market_fees_ix(¶ms, &program_id).unwrap();
assert_eq!(ix.accounts.len(), 3);
assert_eq!(ix.accounts[2].pubkey, market);
assert_eq!(ix.data[0], instruction::SET_MARKET_FEES);
assert_eq!(&ix.data[1..3], &(-10i16).to_le_bytes());
assert_eq!(&ix.data[3..5], &25i16.to_le_bytes());
}
#[test]
fn test_build_set_fee_receiver_ix() {
let program_id = test_program_id();
let params = SetFeeReceiverParams {
authority: Pubkey::new_unique(),
new_fee_receiver: Pubkey::new_unique(),
};
let ix = build_set_fee_receiver_ix(¶ms, &program_id).unwrap();
assert_eq!(ix.accounts.len(), 2);
assert_eq!(ix.data.len(), 33);
assert_eq!(ix.data[0], instruction::SET_FEE_RECEIVER);
assert_eq!(&ix.data[1..33], params.new_fee_receiver.as_ref());
}
#[test]
fn test_build_conditional_metadata_ixs() {
let program_id = test_program_id();
let params = ConditionalMetadataParams {
manager: Pubkey::new_unique(),
market: Pubkey::new_unique(),
deposit_mint: Pubkey::new_unique(),
outcome_index: 1,
name: "Yes".to_string(),
symbol: "YES".to_string(),
uri: "https://example.com/yes.json".to_string(),
};
let create_ix = build_create_conditional_metadata_ix(¶ms, &program_id).unwrap();
assert_eq!(create_ix.accounts.len(), 10);
assert_eq!(create_ix.data[0], instruction::CREATE_CONDITIONAL_METADATA);
assert_eq!(create_ix.data[1], 1);
assert_eq!(
u32::from_le_bytes(create_ix.data[2..6].try_into().unwrap()),
3
);
let update_ix = build_update_conditional_metadata_ix(¶ms, &program_id).unwrap();
assert_eq!(update_ix.accounts.len(), 8);
assert_eq!(update_ix.data[0], instruction::UPDATE_CONDITIONAL_METADATA);
assert!(!update_ix.accounts[0].is_writable);
}
#[test]
fn test_build_match_orders_multi_ix_data_format() {
let program_id = test_program_id();
let operator = Pubkey::new_unique();
let market = Pubkey::new_unique();
let base_mint = Pubkey::new_unique();
let quote_mint = Pubkey::new_unique();
let taker = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_unique(),
market,
base_mint,
quote_mint,
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [1u8; 64],
};
let maker = OrderPayload {
nonce: 2,
salt: 0,
maker: Pubkey::new_unique(),
market,
base_mint,
quote_mint,
side: OrderSide::Ask,
amount_in: 50,
amount_out: 100,
expiration: 0,
signature: [2u8; 64],
};
let params = MatchOrdersMultiParams {
operator,
market,
base_mint,
quote_mint,
fee_receiver: Pubkey::new_unique(),
taker_order: taker,
maker_orders: vec![maker],
maker_fill_amounts: vec![50],
taker_fill_amounts: vec![100],
full_fill_bitmask: 0,
};
let ix = build_match_orders_multi_ix(¶ms, &program_id).unwrap();
assert_eq!(ix.data.len(), 221);
assert_eq!(ix.data[0], instruction::MATCH_ORDERS_MULTI);
assert_eq!(ix.accounts.len(), 19);
}
#[test]
fn test_build_match_orders_multi_ix_full_fill() {
let program_id = test_program_id();
let operator = Pubkey::new_unique();
let market = Pubkey::new_unique();
let base_mint = Pubkey::new_unique();
let quote_mint = Pubkey::new_unique();
let taker = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_unique(),
market,
base_mint,
quote_mint,
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [1u8; 64],
};
let maker = OrderPayload {
nonce: 2,
salt: 0,
maker: Pubkey::new_unique(),
market,
base_mint,
quote_mint,
side: OrderSide::Ask,
amount_in: 50,
amount_out: 100,
expiration: 0,
signature: [2u8; 64],
};
let params = MatchOrdersMultiParams {
operator,
market,
base_mint,
quote_mint,
fee_receiver: Pubkey::new_unique(),
taker_order: taker,
maker_orders: vec![maker],
maker_fill_amounts: vec![50],
taker_fill_amounts: vec![100],
full_fill_bitmask: 0b10000001,
};
let ix = build_match_orders_multi_ix(¶ms, &program_id).unwrap();
assert_eq!(ix.accounts.len(), 17);
}
#[test]
fn test_build_whitelist_deposit_token_ix() {
let program_id = test_program_id();
let params = WhitelistDepositTokenParams {
authority: Pubkey::new_unique(),
mint: Pubkey::new_unique(),
};
let ix = build_whitelist_deposit_token_ix(¶ms, &program_id);
assert_eq!(ix.accounts.len(), 5);
assert!(ix.accounts[1].is_writable);
assert_eq!(ix.data, vec![instruction::WHITELIST_DEPOSIT_TOKEN]);
}
#[test]
fn test_build_deposit_to_global_ix() {
let program_id = test_program_id();
let params = DepositToGlobalParams {
user: Pubkey::new_unique(),
mint: Pubkey::new_unique(),
amount: 1_000_000,
};
let ix = build_deposit_to_global_ix(¶ms, &program_id);
assert_eq!(ix.accounts.len(), 8);
assert_eq!(ix.data.len(), 9);
assert_eq!(ix.data[0], instruction::DEPOSIT_TO_GLOBAL);
}
#[test]
fn test_build_deposit_to_global_ix_with_alt_create() {
let program_id = test_program_id();
let recent_slot = 12345;
let params = DepositToGlobalParams {
user: Pubkey::new_unique(),
mint: Pubkey::new_unique(),
amount: 1_000_000,
};
let (user_nonce, _) = get_user_nonce_pda(¶ms.user, &program_id);
let (lookup_table, _) = get_alt_pda(&user_nonce, recent_slot);
let ix = build_deposit_to_global_ix_with_alt(
¶ms,
DepositToGlobalAltContext::Create { recent_slot },
&program_id,
);
assert_eq!(ix.accounts.len(), 11);
assert_eq!(ix.accounts[8].pubkey, user_nonce);
assert_eq!(ix.accounts[9].pubkey, lookup_table);
assert_eq!(ix.data.len(), 17);
assert_eq!(ix.data[0], instruction::DEPOSIT_TO_GLOBAL);
assert_eq!(&ix.data[9..17], &recent_slot.to_le_bytes());
}
#[test]
fn test_build_withdraw_from_global_ix() {
let program_id = test_program_id();
let params = WithdrawFromGlobalParams {
user: Pubkey::new_unique(),
mint: Pubkey::new_unique(),
amount: 1_000_000,
};
let ix = build_withdraw_from_global_ix(¶ms, &program_id);
assert_eq!(ix.accounts.len(), 7);
assert_eq!(ix.data.len(), 9);
assert_eq!(ix.data[0], instruction::WITHDRAW_FROM_GLOBAL);
}
#[test]
fn test_build_global_to_market_deposit_ix() {
let program_id = test_program_id();
let params = GlobalToMarketDepositParams {
user: Pubkey::new_unique(),
market: Pubkey::new_unique(),
deposit_mint: Pubkey::new_unique(),
amount: 500_000,
};
let ix = build_global_to_market_deposit_ix(¶ms, 3, &program_id);
assert_eq!(ix.accounts.len(), 18);
assert_eq!(ix.data.len(), 9);
assert_eq!(ix.data[0], instruction::GLOBAL_TO_MARKET_DEPOSIT);
}
#[test]
fn test_build_init_position_tokens_ix() {
let program_id = test_program_id();
let deposit_mint = Pubkey::new_unique();
let params = InitPositionTokensParams {
payer: Pubkey::new_unique(),
user: Pubkey::new_unique(),
market: Pubkey::new_unique(),
deposit_mints: vec![deposit_mint],
recent_slot: 12345,
};
let ix = build_init_position_tokens_ix(¶ms, 3, &program_id);
assert_eq!(ix.accounts.len(), 20);
assert_eq!(ix.data.len(), 10); assert_eq!(ix.data[0], instruction::INIT_POSITION_TOKENS);
assert_eq!(ix.data[9], 1); }
#[test]
fn test_build_deposit_and_swap_ix() {
let program_id = test_program_id();
let market = Pubkey::new_unique();
let deposit_mint = Pubkey::new_unique();
let base_mint = Pubkey::new_unique();
let quote_mint = Pubkey::new_unique();
let taker = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_unique(),
market,
base_mint,
quote_mint,
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [1u8; 64],
};
let maker_order = OrderPayload {
nonce: 2,
salt: 0,
maker: Pubkey::new_unique(),
market,
base_mint,
quote_mint,
side: OrderSide::Ask,
amount_in: 50,
amount_out: 100,
expiration: 0,
signature: [2u8; 64],
};
let params = DepositAndSwapParams {
operator: Pubkey::new_unique(),
market,
base_mint,
quote_mint,
fee_receiver: Pubkey::new_unique(),
taker_order: taker,
taker_is_full_fill: false,
taker_is_deposit: true,
taker_deposit_mint: deposit_mint,
num_outcomes: 3,
makers: vec![MakerFill {
order: maker_order,
maker_fill_amount: 50,
taker_fill_amount: 100,
is_full_fill: false,
is_deposit: true,
deposit_mint,
}],
};
let ix = build_deposit_and_swap_ix(¶ms, &program_id).unwrap();
assert_eq!(ix.data.len(), 222);
assert_eq!(ix.data[0], instruction::DEPOSIT_AND_SWAP);
assert_eq!(ix.accounts.len(), 40);
}
#[test]
fn test_build_close_order_status_ix() {
let program_id = test_program_id();
let order_hash = [9u8; 32];
let params = CloseOrderStatusParams {
operator: Pubkey::new_unique(),
order_hash,
};
let ix = build_close_order_status_ix(¶ms, &program_id);
assert_eq!(ix.accounts.len(), 3);
assert_eq!(ix.data.len(), 33);
assert_eq!(ix.data[0], instruction::CLOSE_ORDER_STATUS);
assert_eq!(&ix.data[1..33], &order_hash);
}
#[test]
fn test_build_close_position_token_accounts_ix() {
let program_id = test_program_id();
let market = Pubkey::new_unique();
let position = Pubkey::new_unique();
let deposit_mint = Pubkey::new_unique();
let params = ClosePositionTokenAccountsParams {
operator: Pubkey::new_unique(),
market,
position,
deposit_mints: vec![deposit_mint],
};
let ix = build_close_position_token_accounts_ix(¶ms, 3, &program_id).unwrap();
assert_eq!(ix.accounts.len(), 12);
assert_eq!(ix.data, vec![instruction::CLOSE_POSITION_TOKEN_ACCOUNTS]);
}
#[test]
fn test_build_close_alt_and_orderbook_ixs() {
let program_id = test_program_id();
let operator = Pubkey::new_unique();
let market = Pubkey::new_unique();
let lookup_table = Pubkey::new_unique();
let position_alt = build_close_position_alt_ix(
&ClosePositionAltParams {
operator,
position: Pubkey::new_unique(),
market,
lookup_table,
},
&program_id,
);
assert_eq!(position_alt.accounts.len(), 6);
assert_eq!(position_alt.data, vec![instruction::CLOSE_POSITION_ALT]);
let orderbook = Pubkey::new_unique();
let orderbook_alt = build_close_orderbook_alt_ix(
&CloseOrderbookAltParams {
operator,
orderbook,
market,
lookup_table,
},
&program_id,
);
assert_eq!(orderbook_alt.accounts.len(), 6);
assert_eq!(orderbook_alt.data, vec![instruction::CLOSE_ORDERBOOK_ALT]);
let close_orderbook = build_close_orderbook_ix(
&CloseOrderbookParams {
operator,
orderbook,
market,
lookup_table,
},
&program_id,
);
assert_eq!(close_orderbook.accounts.len(), 5);
assert_eq!(close_orderbook.data, vec![instruction::CLOSE_ORDERBOOK]);
}
}