use core::mem::size_of;
use core::ops::{Deref, DerefMut};
use arrayref::{array_refs, mut_array_refs};
use solana_account_view::{AccountView, Ref, RefMut};
use solana_address::Address;
use solana_hash::Hash;
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use crate::data::intent::{self, EncodedOrderIntent};
use crate::pda::is_pda_with_signer_seeds;
use crate::pda::order::order_pda_signer_seeds;
use crate::{SettlementAccount, SettlementError};
pub const DISCRIMINATOR: u8 = SettlementAccount::OrderAccount.discriminator();
pub const SIZE: usize = 264;
const WIDTH_DISCRIMINATOR: usize = size_of::<u8>();
const WIDTH_BUMP: usize = size_of::<u8>();
const WIDTH_CANCELLED: usize = size_of::<bool>();
const WIDTH_AMOUNT_WITHDRAWN: usize = size_of::<u64>();
const WIDTH_AMOUNT_RECEIVED: usize = size_of::<u64>();
const WIDTH_CREATED_BY: usize = size_of::<Pubkey>();
const WIDTH_INTENT: usize = EncodedOrderIntent::SIZE;
struct OrderSlots<'a> {
discriminator: &'a [u8; WIDTH_DISCRIMINATOR],
bump: &'a [u8; WIDTH_BUMP],
cancelled: &'a [u8; WIDTH_CANCELLED],
amount_withdrawn: &'a [u8; WIDTH_AMOUNT_WITHDRAWN],
amount_received: &'a [u8; WIDTH_AMOUNT_RECEIVED],
created_by: &'a [u8; WIDTH_CREATED_BY],
intent: &'a [u8; WIDTH_INTENT],
}
struct OrderSlotsMut<'a> {
discriminator: &'a mut [u8; WIDTH_DISCRIMINATOR],
bump: &'a mut [u8; WIDTH_BUMP],
cancelled: &'a mut [u8; WIDTH_CANCELLED],
amount_withdrawn: &'a mut [u8; WIDTH_AMOUNT_WITHDRAWN],
amount_received: &'a mut [u8; WIDTH_AMOUNT_RECEIVED],
created_by: &'a mut [u8; WIDTH_CREATED_BY],
intent: &'a mut [u8; WIDTH_INTENT],
}
#[inline]
fn order_slots(body: &[u8; SIZE]) -> OrderSlots<'_> {
let (discriminator, bump, cancelled, amount_withdrawn, amount_received, created_by, intent) = array_refs![
body,
WIDTH_DISCRIMINATOR,
WIDTH_BUMP,
WIDTH_CANCELLED,
WIDTH_AMOUNT_WITHDRAWN,
WIDTH_AMOUNT_RECEIVED,
WIDTH_CREATED_BY,
WIDTH_INTENT
];
OrderSlots {
discriminator,
bump,
cancelled,
amount_withdrawn,
amount_received,
created_by,
intent,
}
}
#[inline]
fn order_slots_mut(body: &mut [u8; SIZE]) -> OrderSlotsMut<'_> {
let (discriminator, bump, cancelled, amount_withdrawn, amount_received, created_by, intent) = mut_array_refs![
body,
WIDTH_DISCRIMINATOR,
WIDTH_BUMP,
WIDTH_CANCELLED,
WIDTH_AMOUNT_WITHDRAWN,
WIDTH_AMOUNT_RECEIVED,
WIDTH_CREATED_BY,
WIDTH_INTENT
];
OrderSlotsMut {
discriminator,
bump,
cancelled,
amount_withdrawn,
amount_received,
created_by,
intent,
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FillAmounts {
pub withdrawn: u64,
pub received: u64,
}
pub struct OrderAccount<T>(T);
impl<T: Deref<Target = [u8]>> OrderAccount<T> {
pub fn attach(bytes: T) -> Result<Self, ProgramError> {
let body: &[u8; SIZE] = (&*bytes)
.try_into()
.map_err(|_| ProgramError::InvalidAccountData)?;
if order_slots(body).discriminator != &[DISCRIMINATOR] {
return Err(ProgramError::InvalidAccountData);
}
Ok(Self(bytes))
}
fn body(&self) -> &[u8; SIZE] {
self.0
.first_chunk::<SIZE>()
.expect("body length is guaranteed by any constructor of `OrderAccount`")
}
pub fn bump(&self) -> u8 {
order_slots(self.body()).bump[0]
}
pub fn bump_slice(&self) -> &[u8; WIDTH_BUMP] {
order_slots(self.body()).bump
}
pub fn cancelled(&self) -> Result<bool, ProgramError> {
match order_slots(self.body()).cancelled {
[0] => Ok(false),
[1] => Ok(true),
_ => Err(ProgramError::InvalidAccountData),
}
}
pub fn filled_amounts(&self) -> FillAmounts {
let slots = order_slots(self.body());
FillAmounts {
withdrawn: u64::from_le_bytes(*slots.amount_withdrawn),
received: u64::from_le_bytes(*slots.amount_received),
}
}
pub fn created_by(&self) -> Pubkey {
Pubkey::new_from_array(*order_slots(self.body()).created_by)
}
pub fn intent_bytes(&self) -> &[u8; EncodedOrderIntent::SIZE] {
order_slots(self.body()).intent
}
pub fn intent_uid(&self) -> Hash {
intent::hash_bytes(self.intent_bytes())
}
pub fn check_pda(&self, address: &Address, program_id: &Address) -> Result<(), ProgramError> {
if !is_pda_with_signer_seeds(
address,
program_id,
order_pda_signer_seeds(&self.intent_uid(), self.bump_slice()),
) {
return Err(SettlementError::AccountNotDerivable.into());
}
Ok(())
}
}
impl<'a> OrderAccount<Ref<'a, [u8]>> {
#[inline]
pub fn load_from_pda(
order_pda: &'a AccountView,
program_id: &Address,
) -> Result<Self, ProgramError> {
let order = Self::attach(order_pda.try_borrow()?)?;
order.check_pda(order_pda.address(), program_id)?;
Ok(order)
}
}
impl<'a> OrderAccount<RefMut<'a, [u8]>> {
#[inline]
pub fn load_from_pda_mut(
order_pda: &'a mut AccountView,
program_id: &Address,
) -> Result<Self, ProgramError> {
let address = *order_pda.address();
let order = Self::attach(order_pda.try_borrow_mut()?)?;
order.check_pda(&address, program_id)?;
Ok(order)
}
}
impl<T: DerefMut<Target = [u8]>> OrderAccount<T> {
pub fn initialize(
mut bytes: T,
bump: u8,
cancelled: bool,
amount_withdrawn: u64,
amount_received: u64,
created_by: &Pubkey,
encoded_intent: &[u8; EncodedOrderIntent::SIZE],
) -> Result<Self, ProgramError> {
{
let body: &mut [u8; SIZE] = (&mut *bytes)
.try_into()
.map_err(|_| ProgramError::AccountDataTooSmall)?;
let slots = order_slots_mut(body);
*slots.discriminator = [DISCRIMINATOR];
*slots.bump = [bump];
*slots.cancelled = [cancelled as u8];
*slots.amount_withdrawn = amount_withdrawn.to_le_bytes();
*slots.amount_received = amount_received.to_le_bytes();
*slots.created_by = created_by.to_bytes();
*slots.intent = *encoded_intent;
}
Ok(Self(bytes))
}
fn body_mut(&mut self) -> &mut [u8; SIZE] {
let bytes: &mut [u8] = &mut self.0;
bytes
.first_chunk_mut::<SIZE>()
.expect("body length is guaranteed by any constructor of `OrderAccount`")
}
pub fn set_amounts(&mut self, amounts: FillAmounts) {
let slots = order_slots_mut(self.body_mut());
*slots.amount_withdrawn = amounts.withdrawn.to_le_bytes();
*slots.amount_received = amounts.received.to_le_bytes();
}
pub fn set_cancelled(&mut self) {
let slots = order_slots_mut(self.body_mut());
*slots.cancelled = [true.into()];
}
}
#[cfg(any(test, feature = "test-fixtures"))]
pub mod fixtures {
use proptest::prelude::*;
use super::{EncodedOrderIntent, OrderAccount, Pubkey, DISCRIMINATOR, SIZE};
use crate::data::intent::fixtures::{arb_intent_bytes, arb_order_intent, sample_intent};
use crate::data::intent::OrderIntent;
pub const DISCRIMINATOR_OFFSET: usize = 0;
pub const CANCELLED_OFFSET: usize = 2;
pub const INTENT_OFFSET: usize = 51;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OrderFields {
pub bump: u8,
pub cancelled: bool,
pub amount_withdrawn: u64,
pub amount_received: u64,
pub created_by: Pubkey,
pub intent: OrderIntent,
}
impl OrderFields {
pub fn encode(&self) -> [u8; SIZE] {
let mut bytes = [0u8; SIZE];
OrderAccount::initialize(
&mut bytes[..],
self.bump,
self.cancelled,
self.amount_withdrawn,
self.amount_received,
&self.created_by,
&EncodedOrderIntent::from(&self.intent),
)
.expect("a full-length buffer initializes");
bytes
}
}
pub fn sample_order_fields(cancelled: bool) -> OrderFields {
OrderFields {
bump: 0xbd,
cancelled,
amount_withdrawn: 0x0112_2334_4556_6778,
amount_received: 0x899a_abbc_cdde_eff0,
created_by: Pubkey::new_from_array([0x42; 32]),
intent: sample_intent(Default::default()),
}
}
pub fn sample_order_bytes(cancelled: bool) -> [u8; SIZE] {
sample_order_fields(cancelled).encode()
}
pub fn arb_order_account() -> impl Strategy<Value = OrderFields> {
(
any::<u8>(),
any::<bool>(),
any::<u64>(),
any::<u64>(),
any::<[u8; 32]>(),
arb_order_intent(),
)
.prop_map(
|(bump, cancelled, amount_withdrawn, amount_received, created_by, intent)| {
OrderFields {
bump,
cancelled,
amount_withdrawn,
amount_received,
created_by: Pubkey::new_from_array(created_by),
intent,
}
},
)
}
pub fn arb_order_bytes() -> impl Strategy<Value = [u8; SIZE]> {
(any::<[u8; SIZE]>(), any::<bool>(), arb_intent_bytes()).prop_map(
|(mut bytes, cancelled, intent)| {
bytes[DISCRIMINATOR_OFFSET] = DISCRIMINATOR;
bytes[CANCELLED_OFFSET] = cancelled as u8;
bytes[INTENT_OFFSET..].copy_from_slice(&intent);
bytes
},
)
}
}
#[cfg(test)]
mod tests {
use super::fixtures::{
sample_order_bytes, sample_order_fields, OrderFields, CANCELLED_OFFSET,
DISCRIMINATOR_OFFSET, INTENT_OFFSET,
};
use super::*;
use crate::data::intent::OrderIntent;
#[test]
fn widths_match_order_fields() {
use core::mem::size_of_val;
let OrderFields {
bump,
cancelled,
amount_withdrawn,
amount_received,
created_by,
intent: _intent,
} = sample_order_fields(false);
assert_eq!(WIDTH_BUMP, size_of_val(&bump));
assert_eq!(WIDTH_CANCELLED, size_of_val(&cancelled));
assert_eq!(WIDTH_AMOUNT_WITHDRAWN, size_of_val(&amount_withdrawn));
assert_eq!(WIDTH_AMOUNT_RECEIVED, size_of_val(&amount_received));
assert_eq!(WIDTH_CREATED_BY, size_of_val(&created_by));
assert_eq!(WIDTH_INTENT, size_of::<EncodedOrderIntent>());
}
#[test]
fn reads_fields_it_was_initialized_with() {
for cancelled in [false, true] {
let fields = sample_order_fields(cancelled);
let bytes = fields.encode();
let OrderFields {
bump,
cancelled,
amount_withdrawn,
amount_received,
created_by,
intent,
} = fields;
let order = OrderAccount::attach(&bytes[..]).expect("sample must attach");
assert_eq!(order.bump(), bump);
assert_eq!(order.cancelled().expect("valid cancelled"), cancelled);
assert_eq!(
order.filled_amounts(),
FillAmounts {
withdrawn: amount_withdrawn,
received: amount_received,
}
);
assert_eq!(order.created_by(), created_by);
assert_eq!(
OrderIntent::try_from(order.intent_bytes()).expect("valid intent"),
intent
);
assert_eq!(order.intent_uid(), intent.uid());
}
}
#[test]
fn set_amounts_writes_only_the_two_amount_fields() {
let mut bytes = sample_order_bytes(false);
let filled = FillAmounts {
withdrawn: 0xdead_beef_dead_beef,
received: 0x0123_4567_89ab_cdef,
};
let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
let current = account.filled_amounts();
assert_ne!(
filled.withdrawn, current.withdrawn,
"sanity check: new withdrawn is different"
);
assert_ne!(
filled.received, current.received,
"sanity check: new received is different"
);
account.set_amounts(filled.clone());
let expected = OrderFields {
amount_withdrawn: filled.withdrawn,
amount_received: filled.received,
..sample_order_fields(false)
}
.encode();
assert_eq!(bytes, expected);
}
#[test]
fn set_cancelled_writes_only_the_cancelled_byte() {
let mut bytes = sample_order_bytes(false);
let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
assert!(
!account.cancelled().expect("valid cancelled"),
"sanity check: order starts active"
);
account.set_cancelled();
let expected = sample_order_fields(true).encode();
assert_eq!(bytes, expected);
}
#[test]
fn set_cancelled_on_cancelled_order_is_a_no_op() {
let mut bytes = sample_order_bytes(true);
let mut account = OrderAccount::attach(&mut bytes[..]).expect("sample must attach");
assert!(
account.cancelled().expect("valid cancelled"),
"sanity check: order starts cancelled"
);
account.set_cancelled();
let expected = sample_order_fields(true).encode();
assert_eq!(bytes, expected);
}
#[test]
fn sanity_check_offsets() {
fn first_differing_byte(lhs: &[u8], rhs: &[u8]) -> Option<usize> {
lhs.iter().zip(rhs).position(|(l, r)| l != r)
}
let base = sample_order_bytes(false);
let cancelled = sample_order_bytes(true);
assert_eq!(
first_differing_byte(&base, &cancelled).expect("should differ in the cancelled byte"),
CANCELLED_OFFSET
);
let encoded_intent: [u8; EncodedOrderIntent::SIZE] =
(&EncodedOrderIntent::from(&sample_order_fields(false).intent)).into();
let bitwise_different_encoded_intent: [u8; EncodedOrderIntent::SIZE] =
encoded_intent.map(|b| b ^ 0x02);
let changed_intent_field =
OrderIntent::try_from(&bitwise_different_encoded_intent).expect("hack should work");
let changed_intent = OrderFields {
intent: changed_intent_field,
..sample_order_fields(false)
}
.encode();
assert_eq!(
first_differing_byte(&base, &changed_intent).expect("should differ in the intent slot"),
INTENT_OFFSET
);
}
#[test]
fn attach_rejects_wrong_discriminator() {
let mut bytes = sample_order_bytes(false);
bytes[DISCRIMINATOR_OFFSET] ^= 0xff;
assert_eq!(
OrderAccount::attach(&bytes[..]).err(),
Some(ProgramError::InvalidAccountData),
);
}
#[test]
fn attach_rejects_wrong_length() {
let bytes = sample_order_bytes(false);
assert_eq!(
OrderAccount::attach(&bytes[..SIZE - 1]).err(),
Some(ProgramError::InvalidAccountData),
);
let too_long = [bytes.as_slice(), [0].as_slice()].concat();
assert_eq!(
OrderAccount::attach(&too_long[..]).err(),
Some(ProgramError::InvalidAccountData),
);
}
#[test]
fn cancelled_rejects_non_boolean_byte() {
let mut bytes = sample_order_bytes(false);
for bad in 0x02u8..=0xff {
bytes[CANCELLED_OFFSET] = bad;
let order =
OrderAccount::attach(&bytes[..]).expect("attach ignores the cancelled byte");
assert_eq!(order.cancelled(), Err(ProgramError::InvalidAccountData));
}
}
mod load_from_pda {
use super::*;
use crate::instruction::fixtures::fake_account_with_data;
use crate::pda::order::find_order_pda;
use crate::ID as PROGRAM_ID;
fn canonical_bytes(cancelled: bool) -> ([u8; SIZE], Address) {
let fields = sample_order_fields(cancelled);
let (pda_address, bump) = find_order_pda(&PROGRAM_ID, &fields.intent.uid());
let bytes = OrderFields { bump, ..fields }.encode();
(bytes, pda_address)
}
#[test]
fn accepts_the_canonical_pda() {
let (bytes, pda_address) = canonical_bytes(false);
let order_pda = fake_account_with_data(pda_address, &bytes[..]);
let order = OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID)
.expect("canonical PDA must load");
assert_eq!(order.bump(), bytes[1]);
}
#[test]
fn rejects_a_non_canonical_address() {
let (bytes, _) = canonical_bytes(false);
let wrong_address = Pubkey::new_from_array([0x42; 32]);
let order_pda = fake_account_with_data(wrong_address, &bytes[..]);
assert_eq!(
OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
Some(SettlementError::AccountNotDerivable.into()),
);
}
#[test]
fn rejects_a_stored_bump_that_does_not_derive_the_address() {
let (mut bytes, pda_address) = canonical_bytes(false);
bytes[1] = bytes[1].wrapping_sub(1);
let order_pda = fake_account_with_data(pda_address, &bytes[..]);
assert_eq!(
OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
Some(SettlementError::AccountNotDerivable.into()),
);
}
#[test]
fn attach_propagates_decode_errors() {
let (mut bytes, pda_address) = canonical_bytes(false);
bytes[DISCRIMINATOR_OFFSET] ^= 0xff;
let order_pda = fake_account_with_data(pda_address, &bytes);
assert_eq!(
OrderAccount::load_from_pda(&order_pda, &PROGRAM_ID).err(),
Some(ProgramError::InvalidAccountData),
);
}
}
mod proptest {
use ::proptest::{prelude::*, test_runner::TestCaseError};
use super::*;
use crate::data::order::fixtures::{arb_order_account, arb_order_bytes};
proptest! {
#[test]
fn reads_match_written_fields(fields in arb_order_account()) {
let bytes = fields.encode();
let order = OrderAccount::attach(&bytes[..])
.map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?;
let OrderFields {
bump,
cancelled,
amount_withdrawn,
amount_received,
created_by,
intent,
} = fields;
prop_assert_eq!(order.bump(), bump);
prop_assert_eq!(order.cancelled().expect("valid cancelled"), cancelled);
prop_assert_eq!(
order.filled_amounts(),
FillAmounts {
withdrawn: amount_withdrawn,
received: amount_received,
}
);
prop_assert_eq!(order.created_by(), created_by);
prop_assert_eq!(order.intent_uid(), intent.uid());
prop_assert_eq!(
OrderIntent::try_from(order.intent_bytes()).expect("valid intent"),
intent
);
}
#[test]
fn bytes_roundtrip(bytes in arb_order_bytes()) {
let order = OrderAccount::attach(&bytes[..])
.map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?;
let filled = order.filled_amounts();
let reencoded = OrderFields {
bump: order.bump(),
cancelled: order
.cancelled()
.map_err(|e| TestCaseError::fail(format!("cancelled: {e:?}")))?,
amount_withdrawn: filled.withdrawn,
amount_received: filled.received,
created_by: order.created_by(),
intent: OrderIntent::try_from(order.intent_bytes())
.map_err(|e| TestCaseError::fail(format!("intent: {e:?}")))?,
}
.encode();
prop_assert_eq!(reencoded, bytes);
}
#[test]
fn set_amounts_matches_a_fresh_encode(
fields in arb_order_account(),
new_withdrawn in any::<u64>(),
new_received in any::<u64>(),
) {
let mut bytes = fields.encode();
OrderAccount::attach(&mut bytes[..])
.map_err(|e| TestCaseError::fail(format!("attach failed: {e:?}")))?
.set_amounts(FillAmounts {
withdrawn: new_withdrawn,
received: new_received,
});
let expected = OrderFields {
amount_withdrawn: new_withdrawn,
amount_received: new_received,
..fields
}
.encode();
prop_assert_eq!(bytes, expected);
}
}
}
}