use solana_instruction::{AccountMeta, Instruction};
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
pub use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
use super::InstructionInputParsing;
use crate::{data::intent::EncodedOrderIntent, SettlementInstruction};
pub struct CreateOrder {
pub program_id: Pubkey,
pub owner: Pubkey,
pub created_by: Pubkey,
pub order_pda: Pubkey,
pub intent_bytes: [u8; EncodedOrderIntent::SIZE],
}
impl From<CreateOrder> for Instruction {
fn from(builder: CreateOrder) -> Self {
let mut data = Vec::with_capacity(1 + EncodedOrderIntent::SIZE);
data.push(SettlementInstruction::CreateOrder.discriminator());
data.extend_from_slice(&builder.intent_bytes);
Instruction {
program_id: builder.program_id,
accounts: vec![
AccountMeta::new_readonly(builder.owner, true),
AccountMeta::new(builder.created_by, true),
AccountMeta::new(builder.order_pda, false),
AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
],
data,
}
}
}
pub struct CreateOrderInput<'a, A> {
pub intent_bytes: [u8; EncodedOrderIntent::SIZE],
pub owner: &'a A,
pub created_by: &'a A,
pub order_pda: &'a A,
}
impl<'a, A> InstructionInputParsing<'a, A> for CreateOrderInput<'a, A> {
const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateOrder;
fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
if instruction_data.len() != EncodedOrderIntent::SIZE {
return Err(ProgramError::InvalidInstructionData);
}
let [owner, created_by, order_pda, _, ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
let intent_bytes: [u8; EncodedOrderIntent::SIZE] =
instruction_data.try_into().expect("length checked above");
Ok(Self {
intent_bytes,
owner,
created_by,
order_pda,
})
}
}
#[cfg(any(test, feature = "test-fixtures"))]
pub mod fixtures {
use solana_address::Address;
use super::{CreateOrder, Instruction};
use crate::data::intent::{
fixtures::sample_intent, EncodedOrderIntent, OrderIntent, OrderKind,
};
pub const DEFAULT_OWNER: Address = Address::new_from_array([0x11; 32]);
pub const NUM_ACCOUNTS: usize = 4;
pub fn valid_intent_bytes() -> [u8; EncodedOrderIntent::SIZE] {
(&EncodedOrderIntent::from(&OrderIntent {
owner: DEFAULT_OWNER,
..sample_intent(OrderKind::Sell, true)
}))
.into()
}
pub fn default_order_data(intent_bytes: &[u8; EncodedOrderIntent::SIZE]) -> Vec<u8> {
let zero = Address::new_from_array([0; 32]);
Instruction::from(CreateOrder {
program_id: zero,
owner: zero,
created_by: zero,
order_pda: zero,
intent_bytes: *intent_bytes,
})
.data
}
}
#[cfg(test)]
mod tests {
use super::fixtures::{default_order_data, valid_intent_bytes, NUM_ACCOUNTS};
use super::*;
use crate::instruction::fixtures::{
fake_account, fake_account_from_array, fake_sequential_accounts,
};
use crate::instruction::tests::{
assert_readonly_nonsigner, assert_readonly_signer, assert_writable_nonsigner,
assert_writable_signer,
};
use solana_account_view::AccountView;
use solana_address::Address;
#[test]
fn create_order_input_parses_valid_input() {
let program_id = Address::new_from_array([21; 32]);
let owner = Address::new_from_array([22; 32]);
let created_by = Address::new_from_array([24; 32]);
let order_pda = Address::new_from_array([23; 32]);
let intent_bytes = valid_intent_bytes();
let data = Instruction::from(CreateOrder {
program_id,
owner,
created_by,
order_pda,
intent_bytes,
})
.data;
let accounts = [
fake_account(owner),
fake_account(created_by),
fake_account(order_pda),
fake_account_from_array([4; 32]),
];
let CreateOrderInput {
intent_bytes: derived_intent_bytes,
owner: derived_owner,
created_by: derived_created_by,
order_pda: derived_order_pda,
} = CreateOrderInput::parse(&data, &accounts).expect("parse should succeed");
assert_eq!(derived_intent_bytes, intent_bytes);
assert_eq!(*derived_order_pda.address(), order_pda);
assert_eq!(*derived_owner.address(), owner);
assert_eq!(*derived_created_by.address(), created_by);
}
#[test]
fn create_order_input_rejects_short_data() {
let intent_bytes = valid_intent_bytes();
let mut data = default_order_data(&intent_bytes);
data.pop();
let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
assert_eq!(
CreateOrderInput::parse(&data, &accounts).err(),
Some(ProgramError::InvalidInstructionData),
);
}
#[test]
fn create_order_input_rejects_long_data() {
let intent_bytes = valid_intent_bytes();
let mut data = default_order_data(&intent_bytes);
data.push(0); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
assert_eq!(
CreateOrderInput::parse(&data, &accounts).err(),
Some(ProgramError::InvalidInstructionData),
);
}
#[test]
fn create_order_input_rejects_missing_accounts() {
let intent_bytes = valid_intent_bytes();
let data = default_order_data(&intent_bytes);
let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
accounts.pop();
assert_eq!(
CreateOrderInput::parse(&data, &accounts).err(),
Some(ProgramError::NotEnoughAccountKeys),
);
}
#[test]
fn instruction_data_has_expected_layout() {
let program_id = Pubkey::new_from_array([1; 32]);
let owner = Pubkey::new_from_array([2; 32]);
let created_by = Pubkey::new_from_array([4; 32]);
let order_pda = Pubkey::new_from_array([3; 32]);
let intent_bytes = [0x42u8; EncodedOrderIntent::SIZE];
let Instruction { data, .. } = CreateOrder {
program_id,
owner,
created_by,
order_pda,
intent_bytes,
}
.into();
assert_eq!(data.len(), 1 + EncodedOrderIntent::SIZE);
assert_eq!(data[0], SettlementInstruction::CreateOrder.discriminator());
assert_eq!(&data[1..], &intent_bytes);
}
#[test]
fn instruction_data_has_expected_accounts() {
let program_id = Pubkey::new_from_array([1; 32]);
let owner = Pubkey::new_from_array([2; 32]);
let created_by = Pubkey::new_from_array([4; 32]);
let order_pda = Pubkey::new_from_array([3; 32]);
let intent_bytes = [0u8; EncodedOrderIntent::SIZE];
let Instruction { accounts, .. } = CreateOrder {
program_id,
owner,
created_by,
order_pda,
intent_bytes,
}
.into();
assert_eq!(accounts.len(), 4);
assert_readonly_signer(&accounts[0], owner);
assert_writable_signer(&accounts[1], created_by);
assert_writable_nonsigner(&accounts[2], order_pda);
assert_readonly_nonsigner(&accounts[3], SYSTEM_PROGRAM_ID);
}
}