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::{
13 recover_counterpart, MaybeMint, TokenProgram, INSTRUCTIONS_SYSVAR_ID, MINT_PLACEHOLDER,
14};
15
16#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct Pull {
20 pub destination: Pubkey,
21 pub amount: u64,
22}
23
24#[cfg_attr(any(test, feature = "test-fixtures"), derive(Default))]
57pub struct BeginSettle<'a> {
58 pub program_id: Pubkey,
59 pub state_pda: Pubkey,
60 pub solver: Pubkey,
61 pub finalize_ix_index: u16,
62 pub auction_id: i64,
66 pub only_token_program: Option<TokenProgram>,
69 pub order_pdas: &'a [Pubkey],
70 pub sell_token_accounts: &'a [Pubkey],
71 pub sell_mints: &'a [Option<Pubkey>],
72 pub pulls: &'a [&'a [Pull]],
73}
74
75impl From<BeginSettle<'_>> for Instruction {
76 fn from(builder: BeginSettle<'_>) -> Self {
77 let BeginSettle {
78 program_id,
79 state_pda,
80 solver,
81 finalize_ix_index,
82 auction_id,
83 only_token_program,
84 order_pdas,
85 sell_token_accounts,
86 sell_mints,
87 pulls,
88 } = builder;
89
90 let mut order: Vec<usize> = (0..order_pdas.len()).collect();
94 order.sort_by_key(|&i| order_pdas[i]);
95
96 let counts: Vec<u8> = order.iter().map(|&i| pulls[i].len() as u8).collect();
97 let amounts: Vec<u8> = order
98 .iter()
99 .flat_map(|&i| pulls[i].iter())
100 .flat_map(|pull| pull.amount.to_le_bytes())
101 .collect();
102 let data = [
103 &[SettlementInstruction::BeginSettle.discriminator()][..],
104 &finalize_ix_index.to_le_bytes()[..],
105 &auction_id.to_le_bytes()[..],
106 &[order_pdas.len() as u8][..],
107 &counts[..],
108 &amounts[..],
109 ]
110 .concat();
111
112 let mut accounts = vec![
116 AccountMeta::new_readonly(solver, true),
117 AccountMeta::new_readonly(INSTRUCTIONS_SYSVAR_ID, false),
118 AccountMeta::new_readonly(state_pda, false),
119 ];
120 accounts.extend(TokenProgram::ALL.map(|program| {
124 let address = if only_token_program.is_none_or(|only| only == program) {
125 program.address()
126 } else {
127 INSTRUCTIONS_SYSVAR_ID
128 };
129 AccountMeta::new_readonly(address, false)
130 }));
131 for &i in &order {
132 accounts.push(AccountMeta::new(order_pdas[i], false));
135 accounts.push(AccountMeta::new(sell_token_accounts[i], false));
138 accounts.push(AccountMeta::new_readonly(
140 sell_mints[i].unwrap_or(MINT_PLACEHOLDER),
141 false,
142 ));
143 for pull in pulls[i] {
144 accounts.push(AccountMeta::new(pull.destination, false));
145 }
146 }
147
148 Instruction {
149 program_id,
150 accounts,
151 data,
152 }
153 }
154}
155
156pub struct SettledOrder<'a, A> {
159 pub order_pda: &'a A,
160 pub sell_token_account: &'a A,
161 pub sell_mint: MaybeMint<'a, A>,
164 pub destinations: &'a [A],
166 pub amounts: &'a [[u8; 8]],
168}
169
170pub struct SettledOrders<'a, A> {
174 order_accounts: &'a [A],
180 counts: &'a [u8],
182 amounts: &'a [[u8; 8]],
185}
186
187impl<'a, A> SettledOrders<'a, A> {
188 pub fn len(&self) -> usize {
190 self.counts.len()
191 }
192
193 pub fn is_empty(&self) -> bool {
194 self.counts.is_empty()
195 }
196
197 #[allow(
199 clippy::arithmetic_side_effects,
200 reason = "offsets are bounded by tx limits"
201 )]
202 pub fn iter(&self) -> impl Iterator<Item = SettledOrder<'a, A>> + '_ {
203 let (counts, amounts) = (self.counts, self.amounts);
204 let mut rest: &'a [A] = self.order_accounts;
208 let mut i = 0usize;
209 let mut amount_offset = 0usize;
210 std::iter::from_fn(move || {
211 if i >= counts.len() {
212 return None;
213 }
214 let count = usize::from(counts[i]);
215 i += 1;
216
217 let (order_pda, tail) = rest.split_first()?;
218 let (sell_token_account, tail) = tail.split_first()?;
219 let (sell_mint, tail) = tail.split_first()?;
220 let (destinations, remainder) = tail.split_at(count);
221 rest = remainder;
222
223 let amount_end = amount_offset + count;
224 let order_amounts = &amounts[amount_offset..amount_end];
225 amount_offset = amount_end;
226
227 Some(SettledOrder {
228 order_pda,
229 sell_token_account,
230 sell_mint: MaybeMint::new(sell_mint),
231 destinations,
232 amounts: order_amounts,
233 })
234 })
235 }
236}
237
238pub struct BeginSettleInput<'a, A> {
245 pub finalize_ix_index: u16,
246 pub auction_id: i64,
250 pub solver_account: &'a A,
251 pub instructions_sysvar_account: &'a A,
252 pub state_pda_account: &'a A,
253 pub orders: SettledOrders<'a, A>,
254}
255
256impl<'a, A> InstructionInputParsing<'a, A> for BeginSettleInput<'a, A> {
260 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::BeginSettle;
261
262 fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
263 let (finalize_ix_index, body) = recover_counterpart(instruction_data)?;
264
265 let [solver_account, instructions_sysvar_account, state_pda_account, _spl_token_program_account, _token_2022_program_account, order_accounts @ ..] =
268 accounts
269 else {
270 return Err(ProgramError::NotEnoughAccountKeys);
271 };
272
273 let (auction_id, body) = body
277 .split_first_chunk::<8>()
278 .ok_or(ProgramError::InvalidInstructionData)?;
279 let auction_id = i64::from_le_bytes(*auction_id);
280
281 let (&order_count, body) = body
287 .split_first()
288 .ok_or(ProgramError::InvalidInstructionData)?;
289 let order_count = usize::from(order_count);
290 let (counts, amount_bytes) = body
291 .split_at_checked(order_count)
292 .ok_or(ProgramError::InvalidInstructionData)?;
293 let (amounts, []) = amount_bytes.as_chunks::<8>() else {
294 return Err(ProgramError::InvalidInstructionData);
295 };
296 let transfer_count = amounts.len();
297
298 let expected_accounts = order_count
302 .checked_mul(3)
303 .and_then(|three_n| three_n.checked_add(transfer_count))
304 .ok_or(ProgramError::InvalidInstructionData)?;
305 if order_accounts.len() != expected_accounts {
306 return Err(SettlementError::AccountCountNotMatchingOrderCount.into());
307 }
308
309 let counts_sum: usize = counts.iter().map(|&c| usize::from(c)).sum();
313 if counts_sum != transfer_count {
314 return Err(SettlementError::TransferCountMismatch.into());
315 }
316
317 Ok(Self {
318 finalize_ix_index,
319 auction_id,
320 instructions_sysvar_account,
321 state_pda_account,
322 solver_account,
323 orders: SettledOrders {
324 order_accounts,
325 counts,
326 amounts,
327 },
328 })
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335 use crate::fixtures::pubkey_from_seed;
336 use crate::instruction::fixtures::{
337 fake_account, fake_account_from_array, fake_sequential_accounts,
338 };
339 use crate::instruction::settle::tests::{ix_data, mint_address};
340 use crate::instruction::tests::{assert_readonly_nonsigner, assert_readonly_signer};
341 use crate::token_program::TokenProgram;
342 use hex_literal::hex;
343 use solana_account_view::AccountView;
344 use solana_address::Address;
345
346 const FIXED_ACCOUNTS: usize = 5;
350
351 const AUCTION_ID: i64 = 0x0102_0304_0506_0708;
354
355 #[test]
356 fn expected_encoding_begin_settle_no_orders() {
357 let program_id = pubkey_from_seed("program id");
358 let state_pda = pubkey_from_seed("state pda");
359 let solver = pubkey_from_seed("solver");
360 let Instruction {
361 program_id: ix_program_id,
362 accounts,
363 data,
364 } = BeginSettle {
365 program_id,
366 state_pda,
367 solver,
368 finalize_ix_index: 0x1337,
369 auction_id: 0x0102_0304_0506_0708,
370 ..Default::default()
371 }
372 .into();
373 assert_eq!(ix_program_id, program_id);
374 assert_eq!(
375 data,
376 ix_data![
377 [SettlementInstruction::BeginSettle.discriminator()],
378 hex!("3713"), hex!("0807060504030201"), [0], ],
382 );
383 assert_eq!(accounts.len(), FIXED_ACCOUNTS);
389 assert_readonly_signer(&accounts[0], solver);
390 assert_readonly_nonsigner(&accounts[1], INSTRUCTIONS_SYSVAR_ID);
391 assert_readonly_nonsigner(&accounts[2], state_pda);
392 assert_readonly_nonsigner(&accounts[3], TokenProgram::SplToken.address());
393 assert_readonly_nonsigner(&accounts[4], TokenProgram::Token2022.address());
394 }
395
396 #[test]
400 fn begin_settle_carries_the_token_program_slots_it_is_given() {
401 for (only_token_program, expected) in [
402 (
403 None,
404 [
405 TokenProgram::SplToken.address(),
406 TokenProgram::Token2022.address(),
407 ],
408 ),
409 (
410 Some(TokenProgram::SplToken),
411 [TokenProgram::SplToken.address(), INSTRUCTIONS_SYSVAR_ID],
412 ),
413 (
414 Some(TokenProgram::Token2022),
415 [INSTRUCTIONS_SYSVAR_ID, TokenProgram::Token2022.address()],
416 ),
417 ] {
418 let Instruction { accounts, .. } = Instruction::from(BeginSettle {
419 program_id: Pubkey::new_unique(),
420 state_pda: Pubkey::new_unique(),
421 solver: Pubkey::new_unique(),
422 only_token_program,
423 ..Default::default()
424 });
425 let slots: Vec<Pubkey> = accounts[3..].iter().map(|meta| meta.pubkey).collect();
426 assert_eq!(
427 slots, expected,
428 "{only_token_program:?} should name just the programs it settles against",
429 );
430 }
431 }
432
433 #[test]
434 fn begin_settle_sorts_orders_by_pda() {
435 let program_id = pubkey_from_seed("program id");
436 let state_pda = pubkey_from_seed("state pda");
437 let solver = pubkey_from_seed("solver");
438 let high_order_pda = Pubkey::new_from_array([0xbb; 32]);
441 let high_sell_token_account = Pubkey::new_from_array([0xa0; 32]);
442 let low_order_pda = Pubkey::new_from_array([0xaa; 32]);
443 let low_sell_token_account = Pubkey::new_from_array([0xb0; 32]);
444 let high_sell_mint = Pubkey::new_from_array([0xc0; 32]);
445 let Instruction { data, accounts, .. } = BeginSettle {
446 program_id,
447 state_pda,
448 solver,
449 finalize_ix_index: 0x1337,
450 auction_id: AUCTION_ID,
451 order_pdas: &[high_order_pda, low_order_pda],
452 sell_token_accounts: &[high_sell_token_account, low_sell_token_account],
453 sell_mints: &[Some(high_sell_mint), None],
455 pulls: &[&[], &[]],
456 ..Default::default()
457 }
458 .into();
459
460 assert_eq!(
461 data,
462 ix_data![
463 [SettlementInstruction::BeginSettle.discriminator()],
464 hex!("3713"), AUCTION_ID.to_le_bytes(), [2], [0, 0], ],
469 );
470
471 let expected: Vec<Pubkey> = vec![
472 solver,
473 INSTRUCTIONS_SYSVAR_ID,
474 state_pda,
475 TokenProgram::SplToken.address(),
476 TokenProgram::Token2022.address(),
477 low_order_pda,
478 low_sell_token_account,
479 MINT_PLACEHOLDER,
480 high_order_pda,
481 high_sell_token_account,
482 high_sell_mint,
483 ];
484 let actual: Vec<Pubkey> = accounts.iter().map(|account| account.pubkey).collect();
485 assert_eq!(actual, expected);
486 let writable: Vec<Pubkey> = accounts
489 .iter()
490 .filter(|account| account.is_writable)
491 .map(|account| account.pubkey)
492 .collect();
493 assert_eq!(
494 writable,
495 vec![
496 low_order_pda,
497 low_sell_token_account,
498 high_order_pda,
499 high_sell_token_account,
500 ],
501 );
502 let (_fixed, order_accounts) = accounts.split_at(FIXED_ACCOUNTS);
504 assert!(order_accounts.iter().all(|account| !account.is_signer));
505 }
506
507 #[test]
508 fn begin_settle_encodes_grouped_transfers() {
509 let program_id = pubkey_from_seed("program id");
510 let state_pda = pubkey_from_seed("state pda");
511 let solver = pubkey_from_seed("solver");
512 let order_a = Pubkey::new_from_array([0x01; 32]);
513 let sell_a = Pubkey::new_from_array([0x02; 32]);
514 let order_b = Pubkey::new_from_array([0x03; 32]);
515 let sell_b = Pubkey::new_from_array([0x04; 32]);
516 let dest_a0 = Pubkey::new_from_array([0x05; 32]);
517 let dest_a1 = Pubkey::new_from_array([0x06; 32]);
518 let dest_b0 = Pubkey::new_from_array([0x07; 32]);
519 let mint_b = Pubkey::new_from_array([0x08; 32]);
520
521 let Instruction { data, accounts, .. } = BeginSettle {
523 program_id,
524 state_pda,
525 solver,
526 finalize_ix_index: 0x1337,
527 auction_id: AUCTION_ID,
528 order_pdas: &[order_a, order_b],
529 sell_token_accounts: &[sell_a, sell_b],
530 sell_mints: &[None, Some(mint_b)],
531 pulls: &[
532 &[
533 Pull {
534 destination: dest_a0,
535 amount: 0x0102,
536 },
537 Pull {
538 destination: dest_a1,
539 amount: 0x0304,
540 },
541 ],
542 &[Pull {
543 destination: dest_b0,
544 amount: 0x0506,
545 }],
546 ],
547 ..Default::default()
548 }
549 .into();
550
551 assert_eq!(
552 data,
553 ix_data![
554 [SettlementInstruction::BeginSettle.discriminator()],
555 hex!("3713"), AUCTION_ID.to_le_bytes(), [2], [2, 1], hex!("0201000000000000"),
561 hex!("0403000000000000"),
562 hex!("0605000000000000"),
563 ],
564 );
565
566 let expected: Vec<Pubkey> = vec![
567 solver,
568 INSTRUCTIONS_SYSVAR_ID,
569 state_pda,
570 TokenProgram::SplToken.address(),
571 TokenProgram::Token2022.address(),
572 order_a,
573 sell_a,
574 MINT_PLACEHOLDER,
575 dest_a0,
576 dest_a1,
577 order_b,
578 sell_b,
579 mint_b,
580 dest_b0,
581 ];
582 let actual: Vec<Pubkey> = accounts.iter().map(|account| account.pubkey).collect();
583 assert_eq!(actual, expected);
584 let writable: Vec<Pubkey> = accounts
586 .iter()
587 .filter(|account| account.is_writable)
588 .map(|account| account.pubkey)
589 .collect();
590 assert_eq!(
591 writable,
592 vec![order_a, sell_a, dest_a0, dest_a1, order_b, sell_b, dest_b0,],
593 );
594 let (_fixed, order_accounts) = accounts.split_at(FIXED_ACCOUNTS);
596 assert!(order_accounts.iter().all(|account| !account.is_signer));
597 }
598
599 #[test]
600 fn begin_settle_input_parses_valid_input() {
601 let sysvar = pubkey_from_seed("sysvar");
602 let state = pubkey_from_seed("state pda");
603 let spl_token_program = pubkey_from_seed("spl token program");
604 let token_2022_program = pubkey_from_seed("token 2022 program");
605 let solver = pubkey_from_seed("solver");
606 let accounts = [
607 fake_account(solver),
608 fake_account(sysvar),
609 fake_account(state),
610 fake_account(spl_token_program),
611 fake_account(token_2022_program),
612 ];
613 let data = ix_data![
614 [SettlementInstruction::BeginSettle.discriminator()],
615 [0x37, 0x13], hex!("0807060504030201"), [0x00], ];
619 let BeginSettleInput {
620 finalize_ix_index,
621 auction_id,
622 solver_account,
623 instructions_sysvar_account,
624 orders,
625 state_pda_account,
626 } = BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
627 assert_eq!(finalize_ix_index, 0x1337);
628 assert_eq!(auction_id, 0x0102_0304_0506_0708);
629 assert_eq!(instructions_sysvar_account.address(), &sysvar);
630 assert_eq!(orders.iter().count(), 0);
631 assert_eq!(state_pda_account.address(), &state);
632 assert_eq!(solver_account.address(), &solver);
633 }
634
635 #[test]
636 fn begin_settle_input_rejects_different_discriminator() {
637 let data = ix_data![
638 [SettlementInstruction::FinalizeSettle.discriminator()],
639 [0, 0], ];
641 let accounts: [AccountView; 0] = [];
642 assert_eq!(
643 BeginSettleInput::parse(&data, &accounts).err(),
644 Some(ProgramError::InvalidInstructionData),
645 );
646 }
647
648 #[test]
649 fn begin_settle_input_rejects_empty_accounts() {
650 let data = ix_data![
651 [SettlementInstruction::BeginSettle.discriminator()],
652 [0, 0], ];
654 let accounts: [AccountView; 0] = [];
655 assert_eq!(
656 BeginSettleInput::parse(&data, &accounts).err(),
657 Some(ProgramError::NotEnoughAccountKeys),
658 );
659 }
660
661 #[test]
662 fn begin_settle_input_rejects_missing_auction_id() {
663 let accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
666 let data = ix_data![
667 [SettlementInstruction::BeginSettle.discriminator()],
668 [0, 0], [0x01, 0x02, 0x03], ];
671 assert_eq!(
672 BeginSettleInput::parse(&data, &accounts).err(),
673 Some(ProgramError::InvalidInstructionData),
674 );
675 }
676
677 #[test]
678 fn begin_settle_input_pairs_orders_with_their_accounts() {
679 let sysvar = pubkey_from_seed("sysvar");
680 let state = pubkey_from_seed("state pda");
681 let spl_token_program = pubkey_from_seed("spl token program");
682 let token_2022_program = pubkey_from_seed("token 2022 program");
683 let solver = pubkey_from_seed("solver");
684 let order_pda = pubkey_from_seed("order pda");
685 let sell_token = pubkey_from_seed("sell token");
686 let sell_mint = pubkey_from_seed("sell mint");
687 let accounts = [
688 fake_account(solver),
689 fake_account(sysvar),
690 fake_account(state),
691 fake_account(spl_token_program),
692 fake_account(token_2022_program),
693 fake_account(order_pda),
694 fake_account(sell_token),
695 fake_account(sell_mint),
696 ];
697 let data = ix_data![
698 [SettlementInstruction::BeginSettle.discriminator()],
699 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [0x01], [0x00], ];
704 let BeginSettleInput {
705 finalize_ix_index,
706 auction_id,
707 solver_account,
708 instructions_sysvar_account,
709 orders,
710 state_pda_account,
711 } = BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
712 assert_eq!(finalize_ix_index, 0x1337);
713 assert_eq!(auction_id, AUCTION_ID);
714 assert_eq!(instructions_sysvar_account.address(), &sysvar);
715 assert_eq!(state_pda_account.address(), &state);
716 assert_eq!(solver_account.address(), &solver);
717
718 let mut orders = orders.iter();
719 let order = orders.next().expect("one settled order");
720 assert_eq!(order.order_pda.address(), &order_pda);
721 assert_eq!(order.sell_token_account.address(), &sell_token);
722 assert_eq!(mint_address(order.sell_mint), &sell_mint);
723 assert_eq!(order.destinations.len(), 0);
724 assert!(orders.next().is_none());
725 }
726
727 #[test]
728 fn begin_settle_input_parses_transfers() {
729 let sysvar = pubkey_from_seed("sysvar");
730 let state = pubkey_from_seed("state pda");
731 let spl_token_program = pubkey_from_seed("spl token program");
732 let token_2022_program = pubkey_from_seed("token 2022 program");
733 let solver = pubkey_from_seed("solver");
734 let order_pda = pubkey_from_seed("order pda");
735 let sell_token = pubkey_from_seed("sell token");
736 let sell_mint = pubkey_from_seed("sell mint");
737 let dest0 = pubkey_from_seed("destination 0");
738 let dest1 = pubkey_from_seed("destination 1");
739 let accounts = [
740 fake_account(solver),
741 fake_account(sysvar),
742 fake_account(state),
743 fake_account(spl_token_program),
744 fake_account(token_2022_program),
745 fake_account(order_pda),
746 fake_account(sell_token),
747 fake_account(sell_mint),
748 fake_account(dest0),
749 fake_account(dest1),
750 ];
751 let data = ix_data![
752 [SettlementInstruction::BeginSettle.discriminator()],
753 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [0x01], [0x02], 0x1122u64.to_le_bytes(),
758 0x3344u64.to_le_bytes(),
759 ];
760
761 let BeginSettleInput { orders, .. } =
762 BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
763
764 let mut orders = orders.iter();
765 let order = orders.next().expect("one settled order");
766 assert_eq!(order.order_pda.address(), &order_pda);
767 assert_eq!(order.sell_token_account.address(), &sell_token);
768 assert_eq!(mint_address(order.sell_mint), &sell_mint);
769 let transfers: Vec<(&Address, u64)> = order
770 .destinations
771 .iter()
772 .zip(order.amounts)
773 .map(|(destination, amount)| (destination.address(), u64::from_le_bytes(*amount)))
774 .collect();
775 assert_eq!(transfers, vec![(&dest0, 0x1122), (&dest1, 0x3344)]);
776 assert!(orders.next().is_none());
777 }
778
779 #[test]
780 fn begin_settle_input_pairs_every_order_with_its_sell_token_account() {
781 const ORDER_COUNT: usize = 16;
782
783 let mut expected: Vec<(Address, Address, Address)> = Vec::new();
784 for i in 0..ORDER_COUNT {
785 let order_pda = pubkey_from_seed(&format!("order pda {i}"));
786 let sell_token = pubkey_from_seed(&format!("sell token {i}"));
787 let sell_mint = pubkey_from_seed(&format!("sell mint {i}"));
788 expected.push((order_pda, sell_token, sell_mint));
789 }
790
791 let mut accounts = vec![
794 fake_account_from_array([0xff; 32]),
795 fake_account_from_array([0xfe; 32]),
796 fake_account_from_array([0xfd; 32]),
797 fake_account_from_array([0xfc; 32]),
798 fake_account_from_array([0xfb; 32]),
799 ];
800 for &(order_pda, sell_token, sell_mint) in &expected {
801 accounts.push(fake_account(order_pda));
802 accounts.push(fake_account(sell_token));
803 accounts.push(fake_account(sell_mint));
804 }
805 let data = ix_data![
808 [SettlementInstruction::BeginSettle.discriminator()],
809 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [ORDER_COUNT as u8], [0u8; ORDER_COUNT],
813 ];
814
815 let parsed = BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
816
817 let actual: Vec<(Address, Address, Address)> = parsed
818 .orders
819 .iter()
820 .map(|order| {
821 assert_eq!(order.destinations.len(), 0);
822 (
823 *order.order_pda.address(),
824 *order.sell_token_account.address(),
825 *mint_address(order.sell_mint),
826 )
827 })
828 .collect();
829 assert_eq!(actual, expected);
830 }
831
832 #[test]
833 fn begin_settle_input_rejects_too_few_accounts() {
834 let accounts = fake_sequential_accounts::<{ FIXED_ACCOUNTS + 2 }>();
839 let data = ix_data![
840 [SettlementInstruction::BeginSettle.discriminator()],
841 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x00], ];
846 assert_eq!(
847 BeginSettleInput::parse(&data, &accounts).err(),
848 Some(SettlementError::AccountCountNotMatchingOrderCount.into()),
849 );
850 }
851
852 #[test]
853 fn begin_settle_input_rejects_too_many_accounts() {
854 let accounts = fake_sequential_accounts::<{ FIXED_ACCOUNTS + 5 }>();
857 let data = ix_data![
858 [SettlementInstruction::BeginSettle.discriminator()],
859 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x01], 0x1122u64.to_le_bytes(),
864 ];
865 assert_eq!(
866 BeginSettleInput::parse(&data, &accounts).err(),
867 Some(SettlementError::AccountCountNotMatchingOrderCount.into()),
868 );
869 }
870
871 #[test]
872 fn begin_settle_input_rejects_counts_not_summing_to_destinations() {
873 let accounts = fake_sequential_accounts::<{ FIXED_ACCOUNTS + 5 }>();
877 let data = ix_data![
878 [SettlementInstruction::BeginSettle.discriminator()],
879 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x01], 0u64.to_le_bytes(),
884 0u64.to_le_bytes(),
885 ];
886 assert_eq!(
887 BeginSettleInput::parse(&data, &accounts).err(),
888 Some(SettlementError::TransferCountMismatch.into()),
889 );
890 }
891
892 #[test]
893 fn begin_settle_input_rejects_missing_order_count() {
894 let accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
897 let data = ix_data![
898 [SettlementInstruction::BeginSettle.discriminator()],
899 [0, 0], AUCTION_ID.to_le_bytes(), ];
902 assert_eq!(
903 BeginSettleInput::parse(&data, &accounts).err(),
904 Some(ProgramError::InvalidInstructionData),
905 );
906 }
907
908 #[test]
909 fn begin_settle_input_rejects_body_too_short_for_counts() {
910 let accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
913 let data = ix_data![
914 [SettlementInstruction::BeginSettle.discriminator()],
915 [0, 0], AUCTION_ID.to_le_bytes(), [0x02], [0x00], ];
920 assert_eq!(
921 BeginSettleInput::parse(&data, &accounts).err(),
922 Some(ProgramError::InvalidInstructionData),
923 );
924 }
925
926 #[test]
927 fn begin_settle_input_rejects_partial_amount() {
928 let accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
931 let data = ix_data![
932 [SettlementInstruction::BeginSettle.discriminator()],
933 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x00], [0x11, 0x22, 0x33, 0x44], ];
939 assert_eq!(
940 BeginSettleInput::parse(&data, &accounts).err(),
941 Some(ProgramError::InvalidInstructionData),
942 );
943 }
944}