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, INSTRUCTIONS_SYSVAR_ID, SPL_TOKEN_PROGRAM_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> {
45 pub program_id: Pubkey,
46 pub state_pda: Pubkey,
47 pub finalize_ix_index: u16,
48 pub auction_id: i64,
52 pub order_pdas: &'a [Pubkey],
53 pub sell_token_accounts: &'a [Pubkey],
54 pub pulls: &'a [&'a [Pull]],
55}
56
57impl From<BeginSettle<'_>> for Instruction {
58 fn from(builder: BeginSettle<'_>) -> Self {
59 let BeginSettle {
60 program_id,
61 state_pda,
62 finalize_ix_index,
63 auction_id,
64 order_pdas,
65 sell_token_accounts,
66 pulls,
67 } = builder;
68
69 let mut order: Vec<usize> = (0..order_pdas.len()).collect();
73 order.sort_by_key(|&i| order_pdas[i]);
74
75 let counts: Vec<u8> = order.iter().map(|&i| pulls[i].len() as u8).collect();
76 let amounts: Vec<u8> = order
77 .iter()
78 .flat_map(|&i| pulls[i].iter())
79 .flat_map(|pull| pull.amount.to_le_bytes())
80 .collect();
81 let data = [
82 &[SettlementInstruction::BeginSettle.discriminator()][..],
83 &finalize_ix_index.to_le_bytes()[..],
84 &auction_id.to_le_bytes()[..],
85 &[order_pdas.len() as u8][..],
86 &counts[..],
87 &amounts[..],
88 ]
89 .concat();
90
91 let mut accounts = vec![
94 AccountMeta::new_readonly(INSTRUCTIONS_SYSVAR_ID, false),
95 AccountMeta::new_readonly(state_pda, false),
96 AccountMeta::new_readonly(SPL_TOKEN_PROGRAM_ID, false),
97 ];
98 for &i in &order {
99 accounts.push(AccountMeta::new(order_pdas[i], false));
102 accounts.push(AccountMeta::new(sell_token_accounts[i], false));
105 for pull in pulls[i] {
106 accounts.push(AccountMeta::new(pull.destination, false));
107 }
108 }
109
110 Instruction {
111 program_id,
112 accounts,
113 data,
114 }
115 }
116}
117
118pub struct SettledOrder<'a, A> {
121 pub order_pda: &'a mut A,
122 pub sell_token_account: &'a A,
123 pub destinations: &'a [A],
125 pub amounts: &'a [[u8; 8]],
127}
128
129pub struct SettledOrders<'a, A> {
133 order_accounts: &'a mut [A],
139 counts: &'a [u8],
141 amounts: &'a [[u8; 8]],
144}
145
146impl<'a, A> SettledOrders<'a, A> {
147 #[allow(
152 clippy::arithmetic_side_effects,
153 reason = "offsets are bounded by tx limits"
154 )]
155 pub fn iter_mut(&mut self) -> impl Iterator<Item = SettledOrder<'_, A>> + '_ {
156 let (counts, amounts) = (self.counts, self.amounts);
157 let mut rest: &mut [A] = self.order_accounts;
160 let mut i = 0usize;
161 let mut amount_offset = 0usize;
162 std::iter::from_fn(move || {
163 if i >= counts.len() {
164 return None;
165 }
166 let count = usize::from(counts[i]);
167 i += 1;
168
169 let taken = core::mem::take(&mut rest);
170 let (order_pda, tail) = taken.split_first_mut()?;
171 let (sell_token_account, tail) = tail.split_first_mut()?;
172 let (destinations, remainder) = tail.split_at_mut(count);
173 rest = remainder;
174
175 let amount_end = amount_offset + count;
176 let order_amounts = &amounts[amount_offset..amount_end];
177 amount_offset = amount_end;
178
179 Some(SettledOrder {
180 order_pda,
181 sell_token_account,
182 destinations,
183 amounts: order_amounts,
184 })
185 })
186 }
187}
188
189pub struct BeginSettleInput<'a, A> {
196 pub finalize_ix_index: u16,
197 pub auction_id: i64,
201 pub instructions_sysvar_account: &'a A,
202 pub state_pda_account: &'a A,
203 pub token_program_account: &'a A,
204 pub orders: SettledOrders<'a, A>,
205}
206
207impl<'a, A> InstructionInputParsing<'a, A> for BeginSettleInput<'a, A> {
211 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::BeginSettle;
212
213 fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
214 let (finalize_ix_index, body) = recover_counterpart(instruction_data)?;
215
216 let [instructions_sysvar_account, state_pda_account, token_program_account, order_accounts @ ..] =
217 accounts
218 else {
219 return Err(ProgramError::NotEnoughAccountKeys);
220 };
221
222 let (auction_id, body) = body
226 .split_first_chunk::<8>()
227 .ok_or(ProgramError::InvalidInstructionData)?;
228 let auction_id = i64::from_le_bytes(*auction_id);
229
230 let (&order_count, body) = body
236 .split_first()
237 .ok_or(ProgramError::InvalidInstructionData)?;
238 let order_count = usize::from(order_count);
239 let (counts, amount_bytes) = body
240 .split_at_checked(order_count)
241 .ok_or(ProgramError::InvalidInstructionData)?;
242 let (amounts, []) = amount_bytes.as_chunks::<8>() else {
243 return Err(ProgramError::InvalidInstructionData);
244 };
245 let transfer_count = amounts.len();
246
247 let expected_accounts = order_count
250 .checked_mul(2)
251 .and_then(|two_n| two_n.checked_add(transfer_count))
252 .ok_or(ProgramError::InvalidInstructionData)?;
253 if order_accounts.len() != expected_accounts {
254 return Err(SettlementError::AccountCountNotMatchingOrderCount.into());
255 }
256
257 let counts_sum: usize = counts.iter().map(|&c| usize::from(c)).sum();
261 if counts_sum != transfer_count {
262 return Err(SettlementError::TransferCountMismatch.into());
263 }
264
265 Ok(Self {
266 finalize_ix_index,
267 auction_id,
268 instructions_sysvar_account,
269 state_pda_account,
270 token_program_account,
271 orders: SettledOrders {
272 order_accounts,
273 counts,
274 amounts,
275 },
276 })
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::instruction::fixtures::{
284 fake_account, fake_account_from_array, fake_sequential_accounts,
285 };
286 use crate::instruction::settle::tests::ix_data;
287 use crate::instruction::tests::assert_readonly_nonsigner;
288 use hex_literal::hex;
289 use solana_account_view::AccountView;
290 use solana_address::Address;
291
292 const FIXED_ACCOUNTS: usize = 3;
295
296 const AUCTION_ID: i64 = 0x0102_0304_0506_0708;
299
300 #[test]
301 fn expected_encoding_begin_settle_no_orders() {
302 let program_id = Pubkey::new_unique();
303 let state_pda = Pubkey::new_unique();
304 let Instruction {
305 program_id: ix_program_id,
306 accounts,
307 data,
308 } = BeginSettle {
309 program_id,
310 state_pda,
311 finalize_ix_index: 0x1337,
312 auction_id: 0x0102_0304_0506_0708,
313 order_pdas: &[],
314 sell_token_accounts: &[],
315 pulls: &[],
316 }
317 .into();
318 assert_eq!(ix_program_id, program_id);
319 assert_eq!(
320 data,
321 ix_data![
322 [SettlementInstruction::BeginSettle.discriminator()],
323 hex!("3713"), hex!("0807060504030201"), [0], ],
327 );
328 assert_eq!(accounts.len(), 3);
333 assert_readonly_nonsigner(&accounts[0], INSTRUCTIONS_SYSVAR_ID);
334 assert_readonly_nonsigner(&accounts[1], state_pda);
335 assert_readonly_nonsigner(&accounts[2], SPL_TOKEN_PROGRAM_ID);
336 }
337
338 #[test]
339 fn begin_settle_sorts_orders_by_pda() {
340 let program_id = Pubkey::new_unique();
341 let state_pda = Pubkey::new_unique();
342 let high_order_pda = Pubkey::new_from_array([0xbb; 32]);
345 let high_sell_token_account = Pubkey::new_from_array([0xa0; 32]);
346 let low_order_pda = Pubkey::new_from_array([0xaa; 32]);
347 let low_sell_token_account = Pubkey::new_from_array([0xb0; 32]);
348 let Instruction { data, accounts, .. } = BeginSettle {
349 program_id,
350 state_pda,
351 finalize_ix_index: 0x1337,
352 auction_id: AUCTION_ID,
353 order_pdas: &[high_order_pda, low_order_pda],
354 sell_token_accounts: &[high_sell_token_account, low_sell_token_account],
355 pulls: &[&[], &[]],
356 }
357 .into();
358
359 assert_eq!(
360 data,
361 ix_data![
362 [SettlementInstruction::BeginSettle.discriminator()],
363 hex!("3713"), AUCTION_ID.to_le_bytes(), [2], [0, 0], ],
368 );
369
370 let expected: Vec<Pubkey> = vec![
371 INSTRUCTIONS_SYSVAR_ID,
372 state_pda,
373 SPL_TOKEN_PROGRAM_ID,
374 low_order_pda,
375 low_sell_token_account,
376 high_order_pda,
377 high_sell_token_account,
378 ];
379 let actual: Vec<Pubkey> = accounts.iter().map(|account| account.pubkey).collect();
380 assert_eq!(actual, expected);
381 let writable: Vec<Pubkey> = accounts
383 .iter()
384 .filter(|account| account.is_writable)
385 .map(|account| account.pubkey)
386 .collect();
387 assert_eq!(
388 writable,
389 vec![
390 low_order_pda,
391 low_sell_token_account,
392 high_order_pda,
393 high_sell_token_account,
394 ],
395 );
396 assert!(accounts.iter().all(|account| !account.is_signer));
397 }
398
399 #[test]
400 fn begin_settle_encodes_grouped_transfers() {
401 let program_id = Pubkey::new_unique();
402 let state_pda = Pubkey::new_unique();
403 let order_a = Pubkey::new_from_array([0x01; 32]);
404 let sell_a = Pubkey::new_from_array([0x02; 32]);
405 let order_b = Pubkey::new_from_array([0x03; 32]);
406 let sell_b = Pubkey::new_from_array([0x04; 32]);
407 let dest_a0 = Pubkey::new_from_array([0x05; 32]);
408 let dest_a1 = Pubkey::new_from_array([0x06; 32]);
409 let dest_b0 = Pubkey::new_from_array([0x07; 32]);
410
411 let Instruction { data, accounts, .. } = BeginSettle {
413 program_id,
414 state_pda,
415 finalize_ix_index: 0x1337,
416 auction_id: AUCTION_ID,
417 order_pdas: &[order_a, order_b],
418 sell_token_accounts: &[sell_a, sell_b],
419 pulls: &[
420 &[
421 Pull {
422 destination: dest_a0,
423 amount: 0x0102,
424 },
425 Pull {
426 destination: dest_a1,
427 amount: 0x0304,
428 },
429 ],
430 &[Pull {
431 destination: dest_b0,
432 amount: 0x0506,
433 }],
434 ],
435 }
436 .into();
437
438 assert_eq!(
439 data,
440 ix_data![
441 [SettlementInstruction::BeginSettle.discriminator()],
442 hex!("3713"), AUCTION_ID.to_le_bytes(), [2], [2, 1], hex!("0201000000000000"),
448 hex!("0403000000000000"),
449 hex!("0605000000000000"),
450 ],
451 );
452
453 let expected: Vec<Pubkey> = vec![
454 INSTRUCTIONS_SYSVAR_ID,
455 state_pda,
456 SPL_TOKEN_PROGRAM_ID,
457 order_a,
458 sell_a,
459 dest_a0,
460 dest_a1,
461 order_b,
462 sell_b,
463 dest_b0,
464 ];
465 let actual: Vec<Pubkey> = accounts.iter().map(|account| account.pubkey).collect();
466 assert_eq!(actual, expected);
467 let writable: Vec<Pubkey> = accounts
469 .iter()
470 .filter(|account| account.is_writable)
471 .map(|account| account.pubkey)
472 .collect();
473 assert_eq!(
474 writable,
475 vec![order_a, sell_a, dest_a0, dest_a1, order_b, sell_b, dest_b0],
476 );
477 assert!(accounts.iter().all(|account| !account.is_signer));
478 }
479
480 #[test]
481 fn begin_settle_input_parses_valid_input() {
482 let sysvar = Address::new_from_array([0x42u8; 32]);
483 let state = Address::new_from_array([0x43u8; 32]);
485 let token_program = Address::new_from_array([0x44u8; 32]);
486 let mut accounts = [
487 fake_account(sysvar),
488 fake_account(state),
489 fake_account(token_program),
490 ];
491 let data = ix_data![
492 [SettlementInstruction::BeginSettle.discriminator()],
493 [0x37, 0x13], hex!("0807060504030201"), [0x00], ];
497 let BeginSettleInput {
498 finalize_ix_index,
499 auction_id,
500 instructions_sysvar_account,
501 mut orders,
502 token_program_account,
503 state_pda_account,
504 } = BeginSettleInput::parse(&data, &mut accounts).expect("parse should succeed");
505 assert_eq!(finalize_ix_index, 0x1337);
506 assert_eq!(auction_id, 0x0102_0304_0506_0708);
507 assert_eq!(instructions_sysvar_account.address(), &sysvar);
508 assert_eq!(orders.iter_mut().count(), 0);
509 assert_eq!(token_program_account.address(), &token_program);
510 assert_eq!(state_pda_account.address(), &state);
511 }
512
513 #[test]
514 fn begin_settle_input_rejects_different_discriminator() {
515 let data = ix_data![
516 [SettlementInstruction::FinalizeSettle.discriminator()],
517 [0, 0], ];
519 let mut accounts: [AccountView; 0] = [];
520 assert_eq!(
521 BeginSettleInput::parse(&data, &mut accounts).err(),
522 Some(ProgramError::InvalidInstructionData),
523 );
524 }
525
526 #[test]
527 fn begin_settle_input_rejects_empty_accounts() {
528 let data = ix_data![
529 [SettlementInstruction::BeginSettle.discriminator()],
530 [0, 0], ];
532 let mut accounts: [AccountView; 0] = [];
533 assert_eq!(
534 BeginSettleInput::parse(&data, &mut accounts).err(),
535 Some(ProgramError::NotEnoughAccountKeys),
536 );
537 }
538
539 #[test]
540 fn begin_settle_input_rejects_missing_auction_id() {
541 let mut accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
544 let data = ix_data![
545 [SettlementInstruction::BeginSettle.discriminator()],
546 [0, 0], [0x01, 0x02, 0x03], ];
549 assert_eq!(
550 BeginSettleInput::parse(&data, &mut accounts).err(),
551 Some(ProgramError::InvalidInstructionData),
552 );
553 }
554
555 #[test]
556 fn begin_settle_input_pairs_orders_with_their_accounts() {
557 let sysvar = Address::new_from_array([1u8; 32]);
558 let state = Address::new_from_array([0xa1u8; 32]);
559 let token_program = Address::new_from_array([0xa2u8; 32]);
560 let order_pda = Address::new_from_array([2u8; 32]);
561 let sell_token = Address::new_from_array([3u8; 32]);
562 let mut accounts = [
563 fake_account(sysvar),
564 fake_account(state),
565 fake_account(token_program),
566 fake_account(order_pda),
567 fake_account(sell_token),
568 ];
569 let data = ix_data![
570 [SettlementInstruction::BeginSettle.discriminator()],
571 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [0x01], [0x00], ];
576 let BeginSettleInput {
577 finalize_ix_index,
578 auction_id,
579 instructions_sysvar_account,
580 mut orders,
581 state_pda_account,
582 token_program_account,
583 } = BeginSettleInput::parse(&data, &mut accounts).expect("parse should succeed");
584 assert_eq!(finalize_ix_index, 0x1337);
585 assert_eq!(auction_id, AUCTION_ID);
586 assert_eq!(instructions_sysvar_account.address(), &sysvar);
587 assert_eq!(token_program_account.address(), &token_program);
588 assert_eq!(state_pda_account.address(), &state);
589
590 let mut orders = orders.iter_mut();
591 let order = orders.next().expect("one settled order");
592 assert_eq!(order.order_pda.address(), &order_pda);
593 assert_eq!(order.sell_token_account.address(), &sell_token);
594 assert_eq!(order.destinations.len(), 0);
595 assert!(orders.next().is_none());
596 }
597
598 #[test]
599 fn begin_settle_input_parses_transfers() {
600 let sysvar = Address::new_from_array([1u8; 32]);
601 let state = Address::new_from_array([0xa1u8; 32]);
602 let token_program = Address::new_from_array([0xa2u8; 32]);
603 let order_pda = Address::new_from_array([2u8; 32]);
604 let sell_token = Address::new_from_array([3u8; 32]);
605 let dest0 = Address::new_from_array([4u8; 32]);
606 let dest1 = Address::new_from_array([5u8; 32]);
607 let mut accounts = [
608 fake_account(sysvar),
609 fake_account(state),
610 fake_account(token_program),
611 fake_account(order_pda),
612 fake_account(sell_token),
613 fake_account(dest0),
614 fake_account(dest1),
615 ];
616 let data = ix_data![
617 [SettlementInstruction::BeginSettle.discriminator()],
618 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [0x01], [0x02], 0x1122u64.to_le_bytes(),
623 0x3344u64.to_le_bytes(),
624 ];
625
626 let BeginSettleInput { mut orders, .. } =
627 BeginSettleInput::parse(&data, &mut accounts).expect("parse should succeed");
628
629 let mut orders = orders.iter_mut();
630 let order = orders.next().expect("one settled order");
631 assert_eq!(order.order_pda.address(), &order_pda);
632 assert_eq!(order.sell_token_account.address(), &sell_token);
633 let transfers: Vec<(&Address, u64)> = order
634 .destinations
635 .iter()
636 .zip(order.amounts)
637 .map(|(destination, amount)| (destination.address(), u64::from_le_bytes(*amount)))
638 .collect();
639 assert_eq!(transfers, vec![(&dest0, 0x1122), (&dest1, 0x3344)]);
640 assert!(orders.next().is_none());
641 }
642
643 #[test]
644 fn begin_settle_input_pairs_every_order_with_its_sell_token_account() {
645 const ORDER_COUNT: usize = 16;
646
647 let mut expected: Vec<(Address, Address)> = Vec::new();
648 for i in 0..ORDER_COUNT {
649 let order_pda = Address::new_from_array([i as u8; 32]);
650 let sell_token = Address::new_from_array([(i + ORDER_COUNT) as u8; 32]);
651 expected.push((order_pda, sell_token));
652 }
653
654 let mut accounts = vec![
657 fake_account_from_array([0xff; 32]),
658 fake_account_from_array([0xfe; 32]),
659 fake_account_from_array([0xfd; 32]),
660 ];
661 for &(order_pda, sell_token) in &expected {
662 accounts.push(fake_account(order_pda));
663 accounts.push(fake_account(sell_token));
664 }
665 let data = ix_data![
668 [SettlementInstruction::BeginSettle.discriminator()],
669 [0x37, 0x13], AUCTION_ID.to_le_bytes(), [ORDER_COUNT as u8], [0u8; ORDER_COUNT],
673 ];
674
675 let mut parsed =
676 BeginSettleInput::parse(&data, &mut accounts).expect("parse should succeed");
677
678 let actual: Vec<(Address, Address)> = parsed
679 .orders
680 .iter_mut()
681 .map(|order| {
682 assert_eq!(order.destinations.len(), 0);
683 (
684 *order.order_pda.address(),
685 *order.sell_token_account.address(),
686 )
687 })
688 .collect();
689 assert_eq!(actual, expected);
690 }
691
692 #[test]
693 fn begin_settle_input_rejects_account_count_mismatch() {
694 let mut accounts = fake_sequential_accounts::<{ FIXED_ACCOUNTS + 1 }>();
699 let data = ix_data![
700 [SettlementInstruction::BeginSettle.discriminator()],
701 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x00], ];
706 assert_eq!(
707 BeginSettleInput::parse(&data, &mut accounts).err(),
708 Some(SettlementError::AccountCountNotMatchingOrderCount.into()),
709 );
710 }
711
712 #[test]
713 fn begin_settle_input_rejects_counts_not_summing_to_destinations() {
714 let mut accounts = fake_sequential_accounts::<{ FIXED_ACCOUNTS + 4 }>();
718 let data = ix_data![
719 [SettlementInstruction::BeginSettle.discriminator()],
720 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x01], 0u64.to_le_bytes(),
725 0u64.to_le_bytes(),
726 ];
727 assert_eq!(
728 BeginSettleInput::parse(&data, &mut accounts).err(),
729 Some(SettlementError::TransferCountMismatch.into()),
730 );
731 }
732
733 #[test]
734 fn begin_settle_input_rejects_missing_order_count() {
735 let mut accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
738 let data = ix_data![
739 [SettlementInstruction::BeginSettle.discriminator()],
740 [0, 0], AUCTION_ID.to_le_bytes(), ];
743 assert_eq!(
744 BeginSettleInput::parse(&data, &mut accounts).err(),
745 Some(ProgramError::InvalidInstructionData),
746 );
747 }
748
749 #[test]
750 fn begin_settle_input_rejects_body_too_short_for_counts() {
751 let mut accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
754 let data = ix_data![
755 [SettlementInstruction::BeginSettle.discriminator()],
756 [0, 0], AUCTION_ID.to_le_bytes(), [0x02], [0x00], ];
761 assert_eq!(
762 BeginSettleInput::parse(&data, &mut accounts).err(),
763 Some(ProgramError::InvalidInstructionData),
764 );
765 }
766
767 #[test]
768 fn begin_settle_input_rejects_partial_amount() {
769 let mut accounts = fake_sequential_accounts::<FIXED_ACCOUNTS>();
772 let data = ix_data![
773 [SettlementInstruction::BeginSettle.discriminator()],
774 [0, 0], AUCTION_ID.to_le_bytes(), [0x01], [0x00], [0x11, 0x22, 0x33, 0x44], ];
780 assert_eq!(
781 BeginSettleInput::parse(&data, &mut accounts).err(),
782 Some(ProgramError::InvalidInstructionData),
783 );
784 }
785}