use solana_program_error::ProgramError;
pub mod add_solver;
pub mod cancel_order;
pub mod create_buffer;
pub mod create_order;
pub mod create_self_order;
pub mod initialize;
pub mod reclaim_buffer;
pub mod reclaim_order;
pub mod remove_solver;
pub mod settle;
pub mod transfer_authority;
#[derive(Clone, Copy, Debug, Eq, PartialEq, num_enum::TryFromPrimitive)]
#[repr(u8)]
#[num_enum(error_type(
name = ProgramError,
constructor = SettlementInstruction::unknown_discriminator,
))]
pub enum SettlementInstruction {
BeginSettle = 0,
FinalizeSettle = 1,
CreateOrder = 2,
Initialize = 3,
CreateBuffer = 4,
ReclaimOrder = 5,
ReclaimBuffer = 6,
TransferAuthority = 7,
AddSolver = 8,
RemoveSolver = 9,
CreateSelfOrder = 10,
CancelOrder = 11,
}
impl SettlementInstruction {
pub fn discriminator(self) -> u8 {
self as u8
}
fn unknown_discriminator(_: u8) -> ProgramError {
ProgramError::InvalidInstructionData
}
}
pub fn recover_discriminator(
instruction_data: &[u8],
) -> Result<(SettlementInstruction, &[u8]), ProgramError> {
let discriminator = instruction_data
.first()
.copied()
.ok_or(ProgramError::InvalidInstructionData)
.and_then(SettlementInstruction::try_from)?;
Ok((discriminator, &instruction_data[1..]))
}
pub trait InstructionInputParsing<'a, A>: Sized {
const DISCRIMINATOR: SettlementInstruction;
fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError>;
fn parse(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
match recover_discriminator(instruction_data)? {
(discriminator, remaining_data) if discriminator == Self::DISCRIMINATOR => {
Self::parse_body(remaining_data, accounts)
}
_ => Err(ProgramError::InvalidInstructionData),
}
}
}
#[cfg(any(test, feature = "test-fixtures"))]
pub mod fixtures {
use solana_account_view::{AccountView, RuntimeAccount};
use solana_address::Address;
use std::{mem, ptr};
pub fn fake_account_from(runtime_account: RuntimeAccount) -> AccountView {
let backing = Box::leak(Box::new(runtime_account));
unsafe { AccountView::new_unchecked(backing as *mut RuntimeAccount) }
}
#[allow(
clippy::arithmetic_side_effects,
reason = "the function is mostly vendored and we don't want to introduce unnecessary changes"
)]
pub unsafe fn make_account_view(
address: Address,
owner: Address,
data: &[u8],
borrow_state: u8,
) -> (AccountView, Vec<u64>) {
let hdr_size = mem::size_of::<RuntimeAccount>();
let total = hdr_size + data.len();
let words = total.div_ceil(8);
let mut backing: Vec<u64> = vec![0u64; words];
assert!(
mem::align_of::<u64>() >= mem::align_of::<RuntimeAccount>(),
"`backing` should be properly aligned to store a `RuntimeAccount` instance"
);
let hdr_ptr = backing.as_mut_ptr() as *mut RuntimeAccount;
ptr::write(
hdr_ptr,
RuntimeAccount {
address,
owner,
borrow_state,
data_len: data.len() as u64,
..Default::default()
},
);
ptr::copy_nonoverlapping(
data.as_ptr(),
(hdr_ptr as *mut u8).add(hdr_size),
data.len(),
);
(AccountView::new_unchecked(hdr_ptr), backing)
}
pub fn fake_account_owned_by(address: Address, owner: Address, data: &[u8]) -> AccountView {
let (account, backing) =
unsafe { make_account_view(address, owner, data, solana_account_view::NOT_BORROWED) };
core::mem::forget(backing);
account
}
pub fn fake_account_with_data(address: Address, data: &[u8]) -> AccountView {
fake_account_owned_by(address, Address::default(), data)
}
pub fn fake_account(address: Address) -> AccountView {
fake_account_with_data(address, &[])
}
pub fn fake_account_from_array(address_array: [u8; 32]) -> AccountView {
fake_account(Address::new_from_array(address_array))
}
pub fn fake_signer(address: Address) -> AccountView {
fake_account_from(RuntimeAccount {
address,
is_signer: 1,
..Default::default()
})
}
pub fn fake_sequential_accounts<const N: usize>() -> [AccountView; N] {
core::array::from_fn(|i| fake_account_from_array([(i as u8).wrapping_add(1); 32]))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AccountMeta, Pubkey};
use solana_account_view::AccountView;
#[track_caller]
pub(crate) fn assert_writable_signer(account: &AccountMeta, pubkey: Pubkey) {
assert_eq!(account.pubkey, pubkey, "unexpected account pubkey");
assert!(account.is_writable, "{} should be writable", account.pubkey);
assert!(account.is_signer, "{} should be a signer", account.pubkey);
}
#[track_caller]
pub(crate) fn assert_writable_nonsigner(account: &AccountMeta, pubkey: Pubkey) {
assert_eq!(account.pubkey, pubkey, "unexpected account pubkey");
assert!(account.is_writable, "{} should be writable", account.pubkey);
assert!(
!account.is_signer,
"{} should not be a signer",
account.pubkey
);
}
#[track_caller]
pub(crate) fn assert_readonly_signer(account: &AccountMeta, pubkey: Pubkey) {
assert_eq!(account.pubkey, pubkey, "unexpected account pubkey");
assert!(
!account.is_writable,
"{} should be read-only",
account.pubkey
);
assert!(account.is_signer, "{} should be a signer", account.pubkey);
}
#[track_caller]
pub(crate) fn assert_readonly_nonsigner(account: &AccountMeta, pubkey: Pubkey) {
assert_eq!(account.pubkey, pubkey, "unexpected account pubkey");
assert!(
!account.is_writable,
"{} should be read-only",
account.pubkey
);
assert!(
!account.is_signer,
"{} should not be a signer",
account.pubkey
);
}
#[test]
fn input_parsing_rejects_different_discriminator() {
struct TestInputParsing {}
impl<'a> InstructionInputParsing<'a, AccountView> for TestInputParsing {
const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::BeginSettle;
fn parse_body(
_instruction_data: &'a [u8],
_accounts: &'a [AccountView],
) -> Result<Self, ProgramError> {
Ok(Self {})
}
}
let mut data = [0; 42];
let different_discriminator = SettlementInstruction::CreateOrder;
assert_ne!(TestInputParsing::DISCRIMINATOR, different_discriminator);
data[0] = different_discriminator.discriminator();
assert_eq!(
TestInputParsing::parse(&data, &[]).err(),
Some(ProgramError::InvalidInstructionData),
);
}
#[test]
fn rejects_empty_payload() {
assert_eq!(
recover_discriminator(&[]),
Err(ProgramError::InvalidInstructionData),
);
}
#[test]
fn rejects_unknown_discriminator() {
assert_eq!(
recover_discriminator(&[42]),
Err(ProgramError::InvalidInstructionData),
);
}
#[test]
fn forwards_trailing_bytes() {
assert!(matches!(
recover_discriminator(&[
SettlementInstruction::BeginSettle.discriminator(),
42 ]),
Ok((SettlementInstruction::BeginSettle, [42])),
));
}
#[test]
fn settlement_instruction_try_from_partitions_all_bytes() {
for i in u8::MIN..=u8::MAX {
match SettlementInstruction::try_from(i) {
Ok(ix) => assert_eq!(ix as u8, i),
Err(err) => assert_eq!(err, ProgramError::InvalidInstructionData),
}
}
}
#[test]
fn settlement_instruction_try_from_matches_begin_settle() {
assert_eq!(
SettlementInstruction::try_from(0),
Ok(SettlementInstruction::BeginSettle)
);
}
}