use solana_account_view::AccountView;
use solana_address::Address;
use solana_program_error::ProgramError;
pub use crate::token_program::TokenProgram;
pub use solana_sdk_ids::sysvar::instructions::ID as INSTRUCTIONS_SYSVAR_ID;
pub const MINT_PLACEHOLDER: solana_pubkey::Pubkey = INSTRUCTIONS_SYSVAR_ID;
pub trait Keyed {
fn key(&self) -> &Address;
}
impl Keyed for AccountView {
fn key(&self) -> &Address {
self.address()
}
}
impl Keyed for Address {
fn key(&self) -> &Address {
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MaybeMint<'a, A>(&'a A);
impl<'a, A> MaybeMint<'a, A> {
pub fn new(slot: &'a A) -> Self {
Self(slot)
}
}
impl<'a, A: Keyed> MaybeMint<'a, A> {
pub fn get(&self) -> Option<&'a A> {
if self.0.key() == &MINT_PLACEHOLDER {
None
} else {
Some(self.0)
}
}
}
mod begin;
mod finalize;
pub use begin::{BeginSettle, BeginSettleInput, Pull, SettledOrder, SettledOrders};
pub use finalize::{
finalize_push_data, FinalizeSettle, FinalizeSettleInput, Push, Pushes, FINALIZE_FIXED_ACCOUNTS,
FINALIZE_PUSH_ACCOUNTS,
};
pub fn recover_counterpart(instruction_data: &[u8]) -> Result<(u16, &[u8]), ProgramError> {
match instruction_data {
[b1, b2, rest @ ..] => Ok((u16::from_le_bytes([*b1, *b2]), rest)),
_ => Err(ProgramError::InvalidInstructionData),
}
}
#[cfg(any(test, feature = "test-fixtures"))]
pub mod fixtures {
use proptest::prelude::*;
use solana_pubkey::Pubkey;
#[derive(Debug)]
pub struct ArbPushes {
pub source_buffers: Vec<Pubkey>,
pub destinations: Vec<Pubkey>,
pub mints: Vec<Option<Pubkey>>,
pub bumps: Vec<u8>,
pub amounts: Vec<u64>,
}
pub fn arb_pushes(
count: impl Into<prop::collection::SizeRange>,
) -> impl Strategy<Value = ArbPushes> {
prop::collection::vec(
(
any::<[u8; 32]>().prop_map(Pubkey::new_from_array),
any::<[u8; 32]>().prop_map(Pubkey::new_from_array),
any::<Option<[u8; 32]>>().prop_map(|mint| mint.map(Pubkey::new_from_array)),
any::<u8>(),
any::<u64>(),
),
count,
)
.prop_map(|pushes| ArbPushes {
source_buffers: pushes.iter().map(|push| push.0).collect(),
destinations: pushes.iter().map(|push| push.1).collect(),
mints: pushes.iter().map(|push| push.2).collect(),
bumps: pushes.iter().map(|push| push.3).collect(),
amounts: pushes.iter().map(|push| push.4).collect(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fixtures::pubkey_from_seed;
use crate::instruction::fixtures::fake_account;
use hex_literal::hex;
use solana_address::Address;
macro_rules! ix_data {
($($chunk:expr),* $(,)?) => {
[$(&$chunk[..]),*].concat()
};
}
pub(crate) use ix_data;
pub(crate) fn mint_address(mint: MaybeMint<'_, AccountView>) -> &Address {
mint.get().expect("a real mint").address()
}
#[test]
fn rejects_empty_payload() {
assert_eq!(
recover_counterpart(&[]),
Err(ProgramError::InvalidInstructionData),
);
}
#[test]
fn rejects_too_short_payload() {
assert_eq!(
recover_counterpart(&[42]),
Err(ProgramError::InvalidInstructionData),
);
}
#[test]
fn returns_trailing_bytes() {
assert_eq!(
recover_counterpart(
&[
&hex!("3713")[..], &[42][..], ]
.concat()
),
Ok((0x1337, [42].as_slice())),
);
}
#[test]
fn maybe_mint_get_resolves_an_account_slot() {
let mint = pubkey_from_seed("a real mint");
let account = fake_account(mint);
assert_eq!(mint_address(MaybeMint::new(&account)), &mint);
let placeholder = fake_account(MINT_PLACEHOLDER);
assert!(MaybeMint::new(&placeholder).get().is_none());
}
#[test]
fn maybe_mint_get_resolves_an_address_slot() {
let mint = pubkey_from_seed("a real mint");
assert_eq!(MaybeMint::new(&mint).get(), Some(&mint));
assert_eq!(MaybeMint::new(&MINT_PLACEHOLDER).get(), None);
}
}