1use std::vec;
4
5use solana_instruction::{AccountMeta, Instruction};
6use solana_program_error::ProgramError;
7use solana_pubkey::Pubkey;
8
9use crate::instruction::InstructionInputParsing;
10use crate::{SettlementError, SettlementInstruction};
11
12use super::{recover_counterpart, TokenProgram, INSTRUCTIONS_SYSVAR_ID};
13
14#[derive(Clone, Debug, Eq, PartialEq)]
17pub struct Pull {
18 pub destination: Pubkey,
19 pub amount: u64,
20}
21
22pub struct BeginSettle<'a> {
50 pub program_id: Pubkey,
51 pub state_pda: Pubkey,
52 pub solver: Pubkey,
53 pub finalize_ix_index: u16,
54 pub auction_id: i64,
58 pub only_token_program: Option<TokenProgram>,
61 pub order_pdas: &'a [Pubkey],
62 pub sell_token_accounts: &'a [Pubkey],
63 pub pulls: &'a [&'a [Pull]],
64}
65
66impl From<BeginSettle<'_>> for Instruction {
67 fn from(builder: BeginSettle<'_>) -> Self {
68 let BeginSettle {
69 program_id,
70 state_pda,
71 solver,
72 finalize_ix_index,
73 auction_id,
74 only_token_program,
75 order_pdas,
76 sell_token_accounts,
77 pulls,
78 } = builder;
79
80 let mut order: Vec<usize> = (0..order_pdas.len()).collect();
84 order.sort_by_key(|&i| order_pdas[i]);
85
86 let counts: Vec<u8> = order.iter().map(|&i| pulls[i].len() as u8).collect();
87 let amounts: Vec<u8> = order
88 .iter()
89 .flat_map(|&i| pulls[i].iter())
90 .flat_map(|pull| pull.amount.to_le_bytes())
91 .collect();
92 let data = [
93 &[SettlementInstruction::BeginSettle.discriminator()][..],
94 &finalize_ix_index.to_le_bytes()[..],
95 &auction_id.to_le_bytes()[..],
96 &[order_pdas.len() as u8][..],
97 &counts[..],
98 &amounts[..],
99 ]
100 .concat();
101
102 let mut accounts = vec![
106 AccountMeta::new_readonly(solver, true),
107 AccountMeta::new_readonly(INSTRUCTIONS_SYSVAR_ID, false),
108 AccountMeta::new_readonly(state_pda, false),
109 ];
110 accounts.extend(TokenProgram::ALL.map(|program| {
114 let address = if only_token_program.is_none_or(|only| only == program) {
115 program.address()
116 } else {
117 INSTRUCTIONS_SYSVAR_ID
118 };
119 AccountMeta::new_readonly(address, false)
120 }));
121 for &i in &order {
122 accounts.push(AccountMeta::new(order_pdas[i], false));
125 accounts.push(AccountMeta::new(sell_token_accounts[i], false));
128 for pull in pulls[i] {
129 accounts.push(AccountMeta::new(pull.destination, false));
130 }
131 }
132
133 Instruction {
134 program_id,
135 accounts,
136 data,
137 }
138 }
139}
140
141pub struct SettledOrder<'a, A> {
144 pub order_pda: &'a A,
145 pub sell_token_account: &'a A,
146 pub destinations: &'a [A],
148 pub amounts: &'a [[u8; 8]],
150}
151
152pub struct SettledOrders<'a, A> {
156 order_accounts: &'a [A],
162 counts: &'a [u8],
164 amounts: &'a [[u8; 8]],
167}
168
169impl<'a, A> SettledOrders<'a, A> {
170 #[allow(
172 clippy::arithmetic_side_effects,
173 reason = "offsets are bounded by tx limits"
174 )]
175 pub fn iter(&self) -> impl Iterator<Item = SettledOrder<'a, A>> + '_ {
176 let (counts, amounts) = (self.counts, self.amounts);
177 let mut rest: &'a [A] = self.order_accounts;
180 let mut i = 0usize;
181 let mut amount_offset = 0usize;
182 std::iter::from_fn(move || {
183 if i >= counts.len() {
184 return None;
185 }
186 let count = usize::from(counts[i]);
187 i += 1;
188
189 let (order_pda, tail) = rest.split_first()?;
190 let (sell_token_account, tail) = tail.split_first()?;
191 let (destinations, remainder) = tail.split_at(count);
192 rest = remainder;
193
194 let amount_end = amount_offset + count;
195 let order_amounts = &amounts[amount_offset..amount_end];
196 amount_offset = amount_end;
197
198 Some(SettledOrder {
199 order_pda,
200 sell_token_account,
201 destinations,
202 amounts: order_amounts,
203 })
204 })
205 }
206}
207
208pub struct BeginSettleInput<'a, A> {
215 pub finalize_ix_index: u16,
216 pub auction_id: i64,
220 pub solver_account: &'a A,
221 pub instructions_sysvar_account: &'a A,
222 pub state_pda_account: &'a A,
223 pub orders: SettledOrders<'a, A>,
224}
225
226impl<'a, A> InstructionInputParsing<'a, A> for BeginSettleInput<'a, A> {
230 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::BeginSettle;
231
232 fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
233 let (finalize_ix_index, body) = recover_counterpart(instruction_data)?;
234
235 let [solver_account, instructions_sysvar_account, state_pda_account, _spl_token_program_account, _token_2022_program_account, order_accounts @ ..] =
238 accounts
239 else {
240 return Err(ProgramError::NotEnoughAccountKeys);
241 };
242
243 let (auction_id, body) = body
247 .split_first_chunk::<8>()
248 .ok_or(ProgramError::InvalidInstructionData)?;
249 let auction_id = i64::from_le_bytes(*auction_id);
250
251 let (&order_count, body) = body
257 .split_first()
258 .ok_or(ProgramError::InvalidInstructionData)?;
259 let order_count = usize::from(order_count);
260 let (counts, amount_bytes) = body
261 .split_at_checked(order_count)
262 .ok_or(ProgramError::InvalidInstructionData)?;
263 let (amounts, []) = amount_bytes.as_chunks::<8>() else {
264 return Err(ProgramError::InvalidInstructionData);
265 };
266 let transfer_count = amounts.len();
267
268 let expected_accounts = order_count
271 .checked_mul(2)
272 .and_then(|two_n| two_n.checked_add(transfer_count))
273 .ok_or(ProgramError::InvalidInstructionData)?;
274 if order_accounts.len() != expected_accounts {
275 return Err(SettlementError::AccountCountNotMatchingOrderCount.into());
276 }
277
278 let counts_sum: usize = counts.iter().map(|&c| usize::from(c)).sum();
282 if counts_sum != transfer_count {
283 return Err(SettlementError::TransferCountMismatch.into());
284 }
285
286 Ok(Self {
287 finalize_ix_index,
288 auction_id,
289 instructions_sysvar_account,
290 state_pda_account,
291 solver_account,
292 orders: SettledOrders {
293 order_accounts,
294 counts,
295 amounts,
296 },
297 })
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use crate::fixtures::pubkey_from_seed;
305 use crate::instruction::fixtures::{
306 fake_account, fake_account_from_array, fake_sequential_accounts,
307 };
308 use crate::instruction::settle::tests::ix_data;
309 use crate::instruction::tests::{assert_readonly_nonsigner, assert_readonly_signer};
310 use crate::token_program::TokenProgram;
311 use hex_literal::hex;
312 use solana_account_view::AccountView;
313 use solana_address::Address;
314
315 const FIXED_ACCOUNTS: usize = 5;
319
320 const AUCTION_ID: i64 = 0x0102_0304_0506_0708;
323
324 #[test]
325 fn expected_encoding_begin_settle_no_orders() {
326 let program_id = pubkey_from_seed("program id");
327 let state_pda = pubkey_from_seed("state pda");
328 let solver = pubkey_from_seed("solver");
329 let Instruction {
330 program_id: ix_program_id,
331 accounts,
332 data,
333 } = BeginSettle {
334 program_id,
335 state_pda,
336 solver,
337 finalize_ix_index: 0x1337,
338 auction_id: 0x0102_0304_0506_0708,
339 only_token_program: None,
340 order_pdas: &[],
341 sell_token_accounts: &[],
342 pulls: &[],
343 }
344 .into();
345 assert_eq!(ix_program_id, program_id);
346 assert_eq!(
347 data,
348 ix_data![
349 [SettlementInstruction::BeginSettle.discriminator()],
350 hex!("3713"), hex!("0807060504030201"), [0], ],
354 );
355 assert_eq!(accounts.len(), FIXED_ACCOUNTS);
361 assert_readonly_signer(&accounts[0], solver);
362 assert_readonly_nonsigner(&accounts[1], INSTRUCTIONS_SYSVAR_ID);
363 assert_readonly_nonsigner(&accounts[2], state_pda);
364 assert_readonly_nonsigner(&accounts[3], TokenProgram::SplToken.address());
365 assert_readonly_nonsigner(&accounts[4], TokenProgram::Token2022.address());
366 }
367
368 #[test]
372 fn begin_settle_carries_the_token_program_slots_it_is_given() {
373 for (only_token_program, expected) in [
374 (
375 None,
376 [
377 TokenProgram::SplToken.address(),
378 TokenProgram::Token2022.address(),
379 ],
380 ),
381 (
382 Some(TokenProgram::SplToken),
383 [TokenProgram::SplToken.address(), INSTRUCTIONS_SYSVAR_ID],
384 ),
385 (
386 Some(TokenProgram::Token2022),
387 [INSTRUCTIONS_SYSVAR_ID, TokenProgram::Token2022.address()],
388 ),
389 ] {
390 let Instruction { accounts, .. } = Instruction::from(BeginSettle {
391 program_id: Pubkey::new_unique(),
392 state_pda: Pubkey::new_unique(),
393 solver: Pubkey::new_unique(),
394 finalize_ix_index: 0,
395 auction_id: 0,
396 only_token_program,
397 order_pdas: &[],
398 sell_token_accounts: &[],
399 pulls: &[],
400 });
401 let slots: Vec<Pubkey> = accounts[3..].iter().map(|meta| meta.pubkey).collect();
402 assert_eq!(
403 slots, expected,
404 "{only_token_program:?} should name just the programs it settles against",
405 );
406 }
407 }
408
409 #[test]
410 fn begin_settle_sorts_orders_by_pda() {
411 let program_id = pubkey_from_seed("program id");
412 let state_pda = pubkey_from_seed("state pda");
413 let solver = pubkey_from_seed("solver");
414 let high_order_pda = Pubkey::new_from_array([0xbb; 32]);
417 let high_sell_token_account = Pubkey::new_from_array([0xa0; 32]);
418 let low_order_pda = Pubkey::new_from_array([0xaa; 32]);
419 let low_sell_token_account = Pubkey::new_from_array([0xb0; 32]);
420 let Instruction { data, accounts, .. } = BeginSettle {
421 program_id,
422 state_pda,
423 solver,
424 finalize_ix_index: 0x1337,
425 auction_id: AUCTION_ID,
426 only_token_program: None,
427 order_pdas: &[high_order_pda, low_order_pda],
428 sell_token_accounts: &[high_sell_token_account, low_sell_token_account],
429 pulls: &[&[], &[]],
430 }
431 .into();
432
433 assert_eq!(
434 data,
435 ix_data![
436 [SettlementInstruction::BeginSettle.discriminator()],
437 hex!("3713"), AUCTION_ID.to_le_bytes(), [2], [0, 0], ],
442 );
443
444 let expected: Vec<Pubkey> = vec![
445 solver,
446 INSTRUCTIONS_SYSVAR_ID,
447 state_pda,
448 TokenProgram::SplToken.address(),
449 TokenProgram::Token2022.address(),
450 low_order_pda,
451 low_sell_token_account,
452 high_order_pda,
453 high_sell_token_account,
454 ];
455 let actual: Vec<Pubkey> = accounts.iter().map(|account| account.pubkey).collect();
456 assert_eq!(actual, expected);
457 let writable: Vec<Pubkey> = accounts
459 .iter()
460 .filter(|account| account.is_writable)
461 .map(|account| account.pubkey)
462 .collect();
463 assert_eq!(
464 writable,
465 vec![
466 low_order_pda,
467 low_sell_token_account,
468 high_order_pda,
469 high_sell_token_account,
470 ],
471 );
472 let (_fixed, order_accounts) = accounts.split_at(FIXED_ACCOUNTS);
474 assert!(order_accounts.iter().all(|account| !account.is_signer));
475 }
476
477 #[test]
478 fn begin_settle_encodes_grouped_transfers() {
479 let program_id = pubkey_from_seed("program id");
480 let state_pda = pubkey_from_seed("state pda");
481 let solver = pubkey_from_seed("solver");
482 let order_a = Pubkey::new_from_array([0x01; 32]);
483 let sell_a = Pubkey::new_from_array([0x02; 32]);
484 let order_b = Pubkey::new_from_array([0x03; 32]);
485 let sell_b = Pubkey::new_from_array([0x04; 32]);
486 let dest_a0 = Pubkey::new_from_array([0x05; 32]);
487 let dest_a1 = Pubkey::new_from_array([0x06; 32]);
488 let dest_b0 = Pubkey::new_from_array([0x07; 32]);
489
490 let Instruction { data, accounts, .. } = BeginSettle {
492 program_id,
493 state_pda,
494 solver,
495 finalize_ix_index: 0x1337,
496 auction_id: AUCTION_ID,
497 only_token_program: None,
498 order_pdas: &[order_a, order_b],
499 sell_token_accounts: &[sell_a, sell_b],
500 pulls: &[
501 &[
502 Pull {
503 destination: dest_a0,
504 amount: 0x0102,
505 },
506 Pull {
507 destination: dest_a1,
508 amount: 0x0304,
509 },
510 ],
511 &[Pull {
512 destination: dest_b0,
513 amount: 0x0506,
514 }],
515 ],
516 }
517 .into();
518
519 assert_eq!(
520 data,
521 ix_data![
522 [SettlementInstruction::BeginSettle.discriminator()],
523 hex!("3713"), AUCTION_ID.to_le_bytes(), [2], [2, 1], hex!("0201000000000000"),
529 hex!("0403000000000000"),
530 hex!("0605000000000000"),
531 ],
532 );
533
534 let expected: Vec<Pubkey> = vec![
535 solver,
536 INSTRUCTIONS_SYSVAR_ID,
537 state_pda,
538 TokenProgram::SplToken.address(),
539 TokenProgram::Token2022.address(),
540 order_a,
541 sell_a,
542 dest_a0,
543 dest_a1,
544 order_b,
545 sell_b,
546 dest_b0,
547 ];
548 let actual: Vec<Pubkey> = accounts.iter().map(|account| account.pubkey).collect();
549 assert_eq!(actual, expected);
550 let writable: Vec<Pubkey> = accounts
552 .iter()
553 .filter(|account| account.is_writable)
554 .map(|account| account.pubkey)
555 .collect();
556 assert_eq!(
557 writable,
558 vec![order_a, sell_a, dest_a0, dest_a1, order_b, sell_b, dest_b0],
559 );
560 let (_fixed, order_accounts) = accounts.split_at(FIXED_ACCOUNTS);
562 assert!(order_accounts.iter().all(|account| !account.is_signer));
563 }
564
565 #[test]
566 fn begin_settle_input_parses_valid_input() {
567 let sysvar = pubkey_from_seed("sysvar");
568 let state = pubkey_from_seed("state pda");
569 let spl_token_program = pubkey_from_seed("spl token program");
570 let token_2022_program = pubkey_from_seed("token 2022 program");
571 let solver = pubkey_from_seed("solver");
572 let accounts = [
573 fake_account(solver),
574 fake_account(sysvar),
575 fake_account(state),
576 fake_account(spl_token_program),
577 fake_account(token_2022_program),
578 ];
579 let data = ix_data![
580 [SettlementInstruction::BeginSettle.discriminator()],
581 [0x37, 0x13], hex!("0807060504030201"), [0x00], ];
585 let BeginSettleInput {
586 finalize_ix_index,
587 auction_id,
588 solver_account,
589 instructions_sysvar_account,
590 orders,
591 state_pda_account,
592 } = BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
593 assert_eq!(finalize_ix_index, 0x1337);
594 assert_eq!(auction_id, 0x0102_0304_0506_0708);
595 assert_eq!(instructions_sysvar_account.address(), &sysvar);
596 assert_eq!(orders.iter().count(), 0);
597 assert_eq!(state_pda_account.address(), &state);
598 assert_eq!(solver_account.address(), &solver);
599 }
600
601 #[test]
602 fn begin_settle_input_rejects_different_discriminator() {
603 let data = ix_data![
604 [SettlementInstruction::FinalizeSettle.discriminator()],
605 [0, 0], ];
607 let accounts: [AccountView; 0] = [];
608 assert_eq!(
609 BeginSettleInput::parse(&data, &accounts).err(),
610 Some(ProgramError::InvalidInstructionData),
611 );
612 }
613
614 #[test]
615 fn begin_settle_input_rejects_empty_accounts() {
616 let data = ix_data![
617 [SettlementInstruction::BeginSettle.discriminator()],
618 [0, 0], ];
620 let accounts: [AccountView; 0] = [];
621 assert_eq!(
622 BeginSettleInput::parse(&data, &accounts).err(),
623 Some(ProgramError::NotEnoughAccountKeys),
624 );
625 }
626
627 #[test]
628 fn begin_settle_input_rejects_missing_auction_id() {
629 let accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
632 let data = ix_data![
633 [SettlementInstruction::BeginSettle.discriminator()],
634 [0, 0], [0x01, 0x02, 0x03], ];
637 assert_eq!(
638 BeginSettleInput::parse(&data, &accounts).err(),
639 Some(ProgramError::InvalidInstructionData),
640 );
641 }
642
643 #[test]
644 fn begin_settle_input_pairs_orders_with_their_accounts() {
645 let sysvar = pubkey_from_seed("sysvar");
646 let state = pubkey_from_seed("state pda");
647 let spl_token_program = pubkey_from_seed("spl token program");
648 let token_2022_program = pubkey_from_seed("token 2022 program");
649 let solver = pubkey_from_seed("solver");
650 let order_pda = pubkey_from_seed("order pda");
651 let sell_token = pubkey_from_seed("sell token");
652 let accounts = [
653 fake_account(solver),
654 fake_account(sysvar),
655 fake_account(state),
656 fake_account(spl_token_program),
657 fake_account(token_2022_program),
658 fake_account(order_pda),
659 fake_account(sell_token),
660 ];
661 let data = ix_data![
662 [SettlementInstruction::BeginSettle.discriminator()],
663 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [0x01], [0x00], ];
668 let BeginSettleInput {
669 finalize_ix_index,
670 auction_id,
671 solver_account,
672 instructions_sysvar_account,
673 orders,
674 state_pda_account,
675 } = BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
676 assert_eq!(finalize_ix_index, 0x1337);
677 assert_eq!(auction_id, AUCTION_ID);
678 assert_eq!(instructions_sysvar_account.address(), &sysvar);
679 assert_eq!(state_pda_account.address(), &state);
680 assert_eq!(solver_account.address(), &solver);
681
682 let mut orders = orders.iter();
683 let order = orders.next().expect("one settled order");
684 assert_eq!(order.order_pda.address(), &order_pda);
685 assert_eq!(order.sell_token_account.address(), &sell_token);
686 assert_eq!(order.destinations.len(), 0);
687 assert!(orders.next().is_none());
688 }
689
690 #[test]
691 fn begin_settle_input_parses_transfers() {
692 let sysvar = pubkey_from_seed("sysvar");
693 let state = pubkey_from_seed("state pda");
694 let spl_token_program = pubkey_from_seed("spl token program");
695 let token_2022_program = pubkey_from_seed("token 2022 program");
696 let solver = pubkey_from_seed("solver");
697 let order_pda = pubkey_from_seed("order pda");
698 let sell_token = pubkey_from_seed("sell token");
699 let dest0 = pubkey_from_seed("destination 0");
700 let dest1 = pubkey_from_seed("destination 1");
701 let accounts = [
702 fake_account(solver),
703 fake_account(sysvar),
704 fake_account(state),
705 fake_account(spl_token_program),
706 fake_account(token_2022_program),
707 fake_account(order_pda),
708 fake_account(sell_token),
709 fake_account(dest0),
710 fake_account(dest1),
711 ];
712 let data = ix_data![
713 [SettlementInstruction::BeginSettle.discriminator()],
714 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [0x01], [0x02], 0x1122u64.to_le_bytes(),
719 0x3344u64.to_le_bytes(),
720 ];
721
722 let BeginSettleInput { orders, .. } =
723 BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
724
725 let mut orders = orders.iter();
726 let order = orders.next().expect("one settled order");
727 assert_eq!(order.order_pda.address(), &order_pda);
728 assert_eq!(order.sell_token_account.address(), &sell_token);
729 let transfers: Vec<(&Address, u64)> = order
730 .destinations
731 .iter()
732 .zip(order.amounts)
733 .map(|(destination, amount)| (destination.address(), u64::from_le_bytes(*amount)))
734 .collect();
735 assert_eq!(transfers, vec![(&dest0, 0x1122), (&dest1, 0x3344)]);
736 assert!(orders.next().is_none());
737 }
738
739 #[test]
740 fn begin_settle_input_pairs_every_order_with_its_sell_token_account() {
741 const ORDER_COUNT: usize = 16;
742
743 let mut expected: Vec<(Address, Address)> = Vec::new();
744 for i in 0..ORDER_COUNT {
745 let order_pda = pubkey_from_seed(&format!("order pda {i}"));
746 let sell_token = pubkey_from_seed(&format!("sell token {i}"));
747 expected.push((order_pda, sell_token));
748 }
749
750 let mut accounts = vec![
753 fake_account_from_array([0xff; 32]),
754 fake_account_from_array([0xfe; 32]),
755 fake_account_from_array([0xfd; 32]),
756 fake_account_from_array([0xfc; 32]),
757 fake_account_from_array([0xfb; 32]),
758 ];
759 for &(order_pda, sell_token) in &expected {
760 accounts.push(fake_account(order_pda));
761 accounts.push(fake_account(sell_token));
762 }
763 let data = ix_data![
766 [SettlementInstruction::BeginSettle.discriminator()],
767 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [ORDER_COUNT as u8], [0u8; ORDER_COUNT],
771 ];
772
773 let parsed = BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
774
775 let actual: Vec<(Address, Address)> = parsed
776 .orders
777 .iter()
778 .map(|order| {
779 assert_eq!(order.destinations.len(), 0);
780 (
781 *order.order_pda.address(),
782 *order.sell_token_account.address(),
783 )
784 })
785 .collect();
786 assert_eq!(actual, expected);
787 }
788
789 #[test]
790 fn begin_settle_input_rejects_account_count_mismatch() {
791 let accounts = fake_sequential_accounts::<{ FIXED_ACCOUNTS + 1 }>();
796 let data = ix_data![
797 [SettlementInstruction::BeginSettle.discriminator()],
798 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x00], ];
803 assert_eq!(
804 BeginSettleInput::parse(&data, &accounts).err(),
805 Some(SettlementError::AccountCountNotMatchingOrderCount.into()),
806 );
807 }
808
809 #[test]
810 fn begin_settle_input_rejects_counts_not_summing_to_destinations() {
811 let accounts = fake_sequential_accounts::<{ FIXED_ACCOUNTS + 4 }>();
815 let data = ix_data![
816 [SettlementInstruction::BeginSettle.discriminator()],
817 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x01], 0u64.to_le_bytes(),
822 0u64.to_le_bytes(),
823 ];
824 assert_eq!(
825 BeginSettleInput::parse(&data, &accounts).err(),
826 Some(SettlementError::TransferCountMismatch.into()),
827 );
828 }
829
830 #[test]
831 fn begin_settle_input_rejects_missing_order_count() {
832 let accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
835 let data = ix_data![
836 [SettlementInstruction::BeginSettle.discriminator()],
837 [0, 0], AUCTION_ID.to_le_bytes(), ];
840 assert_eq!(
841 BeginSettleInput::parse(&data, &accounts).err(),
842 Some(ProgramError::InvalidInstructionData),
843 );
844 }
845
846 #[test]
847 fn begin_settle_input_rejects_body_too_short_for_counts() {
848 let accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
851 let data = ix_data![
852 [SettlementInstruction::BeginSettle.discriminator()],
853 [0, 0], AUCTION_ID.to_le_bytes(), [0x02], [0x00], ];
858 assert_eq!(
859 BeginSettleInput::parse(&data, &accounts).err(),
860 Some(ProgramError::InvalidInstructionData),
861 );
862 }
863
864 #[test]
865 fn begin_settle_input_rejects_partial_amount() {
866 let accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
869 let data = ix_data![
870 [SettlementInstruction::BeginSettle.discriminator()],
871 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x00], [0x11, 0x22, 0x33, 0x44], ];
877 assert_eq!(
878 BeginSettleInput::parse(&data, &accounts).err(),
879 Some(ProgramError::InvalidInstructionData),
880 );
881 }
882}