1use core::mem::size_of;
24use core::ops::{Deref, DerefMut};
25
26use arrayref::{array_refs, mut_array_refs};
27use solana_account_view::{AccountView, Ref};
28use solana_program_error::ProgramError;
29use solana_pubkey::Pubkey;
30
31use crate::{Role, SettlementAccount, SettlementError};
32
33pub const DISCRIMINATOR: u8 = SettlementAccount::SettlementState.discriminator();
35
36const WIDTH_DISCRIMINATOR: usize = size_of::<u8>();
38
39pub const WIDTH_PUBKEY: usize = size_of::<Pubkey>();
42
43pub const WIDTH_HEADER: usize = WIDTH_DISCRIMINATOR + 3 * WIDTH_PUBKEY;
46
47struct HeaderSlots<'a> {
51 discriminator: &'a [u8; WIDTH_DISCRIMINATOR],
52 manager: &'a [u8; WIDTH_PUBKEY],
53 reclaim_authority: &'a [u8; WIDTH_PUBKEY],
54 self_order_authority: &'a [u8; WIDTH_PUBKEY],
55}
56
57struct HeaderSlotsMut<'a> {
59 discriminator: &'a mut [u8; WIDTH_DISCRIMINATOR],
60 manager: &'a mut [u8; WIDTH_PUBKEY],
61 reclaim_authority: &'a mut [u8; WIDTH_PUBKEY],
62 self_order_authority: &'a mut [u8; WIDTH_PUBKEY],
63}
64
65#[inline]
67fn header_slots(header: &[u8; WIDTH_HEADER]) -> HeaderSlots<'_> {
68 let (discriminator, manager, reclaim_authority, self_order_authority) = array_refs![
69 header,
70 WIDTH_DISCRIMINATOR,
71 WIDTH_PUBKEY,
72 WIDTH_PUBKEY,
73 WIDTH_PUBKEY
74 ];
75 HeaderSlots {
76 discriminator,
77 manager,
78 reclaim_authority,
79 self_order_authority,
80 }
81}
82
83#[inline]
85fn header_slots_mut(header: &mut [u8; WIDTH_HEADER]) -> HeaderSlotsMut<'_> {
86 let (discriminator, manager, reclaim_authority, self_order_authority) = mut_array_refs![
87 header,
88 WIDTH_DISCRIMINATOR,
89 WIDTH_PUBKEY,
90 WIDTH_PUBKEY,
91 WIDTH_PUBKEY
92 ];
93 HeaderSlotsMut {
94 discriminator,
95 manager,
96 reclaim_authority,
97 self_order_authority,
98 }
99}
100
101#[derive(Clone, Debug, Eq, PartialEq)]
103pub struct StateInitArgs {
104 pub manager: Pubkey,
106 pub reclaim_authority: Pubkey,
108 pub self_order_authority: Pubkey,
110}
111
112pub struct StateAccount<T>(T);
119
120impl<T: Deref<Target = [u8]>> StateAccount<T> {
121 pub fn attach(bytes: T) -> Result<Self, ProgramError> {
125 let header = bytes
126 .first_chunk::<WIDTH_HEADER>()
127 .ok_or(ProgramError::InvalidAccountData)?;
128 if header_slots(header).discriminator != &[DISCRIMINATOR] {
129 return Err(ProgramError::InvalidAccountData);
130 }
131 Ok(Self(bytes))
132 }
133
134 fn header(&self) -> &[u8; WIDTH_HEADER] {
135 self.0
136 .first_chunk::<WIDTH_HEADER>()
137 .expect("header length is guaranteed by any constructor of `StateAccount`")
138 }
139
140 pub fn authority(&self, role: Role) -> Pubkey {
142 let slots = header_slots(self.header());
143 let holder = match role {
144 Role::Manager => slots.manager,
145 Role::ReclaimAuthority => slots.reclaim_authority,
146 Role::SelfOrderAuthority => slots.self_order_authority,
147 };
148 Pubkey::new_from_array(*holder)
149 }
150
151 fn solver_region(&self) -> &[[u8; WIDTH_PUBKEY]] {
155 let region = self.0.get(WIDTH_HEADER..).unwrap_or_default();
156 let (solvers, _partial) = region.as_chunks::<WIDTH_PUBKEY>();
157 solvers
158 }
159
160 pub fn solver_search(&self, solver: &Pubkey) -> Result<usize, usize> {
164 let seek = solver.to_bytes();
165 self.solver_region().binary_search(&seek)
166 }
167
168 pub fn is_solver(&self, solver: &Pubkey) -> bool {
170 self.solver_search(solver).is_ok()
171 }
172
173 pub fn solvers(&self) -> impl Iterator<Item = Pubkey> + '_ {
175 self.solver_region()
176 .iter()
177 .map(|raw| Pubkey::new_from_array(*raw))
178 }
179
180 pub fn grown_len(&self) -> Result<usize, ProgramError> {
189 self.0
190 .len()
191 .checked_add(WIDTH_PUBKEY)
192 .ok_or(ProgramError::ArithmeticOverflow)
193 }
194}
195
196impl<'a> StateAccount<Ref<'a, [u8]>> {
197 pub fn from_account(account: &'a AccountView) -> Result<Self, ProgramError> {
198 Self::attach(account.try_borrow()?)
199 }
200}
201
202impl<T: DerefMut<Target = [u8]>> StateAccount<T> {
203 pub fn initialize(mut bytes: T, args: &StateInitArgs) -> Result<Self, ProgramError> {
209 {
210 let slots = header_slots_mut(
211 bytes
212 .first_chunk_mut::<WIDTH_HEADER>()
213 .ok_or(ProgramError::AccountDataTooSmall)?,
214 );
215 *slots.discriminator = [DISCRIMINATOR];
216 *slots.manager = args.manager.to_bytes();
217 *slots.reclaim_authority = args.reclaim_authority.to_bytes();
218 *slots.self_order_authority = args.self_order_authority.to_bytes();
219 }
220 Ok(Self(bytes))
221 }
222
223 fn header_mut(&mut self) -> &mut [u8; WIDTH_HEADER] {
224 let bytes: &mut [u8] = &mut self.0;
225 bytes
226 .first_chunk_mut::<WIDTH_HEADER>()
227 .expect("header length is guaranteed by `new`")
228 }
229
230 pub fn set_authority(&mut self, role: Role, new: &Pubkey) {
232 let slots = header_slots_mut(self.header_mut());
233 let holder = match role {
234 Role::Manager => slots.manager,
235 Role::ReclaimAuthority => slots.reclaim_authority,
236 Role::SelfOrderAuthority => slots.self_order_authority,
237 };
238 *holder = new.to_bytes();
239 }
240
241 pub fn insert_solver(&mut self, solver: &Pubkey) -> Result<(), ProgramError> {
251 let (_spare, occupied) = self
252 .solver_region()
253 .split_last()
254 .expect("account grown by one solver slot before insertion");
255 let index = match occupied.binary_search(&solver.to_bytes()) {
256 Ok(_) => return Err(SettlementError::SolverAlreadyExists.into()),
257 Err(index) => index,
258 };
259
260 let data: &mut [u8] = &mut self.0;
263 let occupied_end = data
264 .len()
265 .checked_sub(WIDTH_PUBKEY)
266 .expect("account grown by one solver slot before insertion");
267 let gap = WIDTH_HEADER
268 .checked_add(
269 index
270 .checked_mul(WIDTH_PUBKEY)
271 .expect("insertion index bound by account length"),
272 )
273 .expect("insertion offset bound by account length");
274 let gap_end = gap
275 .checked_add(WIDTH_PUBKEY)
276 .expect("insertion slot bound by account length");
277
278 data.copy_within(gap..occupied_end, gap_end);
279 data[gap..gap_end].copy_from_slice(&solver.to_bytes());
280 Ok(())
281 }
282
283 pub fn remove_solver(&mut self, solver: &Pubkey) -> Result<usize, ProgramError> {
291 let index = match self.solver_region().binary_search(&solver.to_bytes()) {
292 Ok(index) => index,
293 Err(_) => return Err(SettlementError::SolverNotFound.into()),
294 };
295
296 let data: &mut [u8] = &mut self.0;
299 let len = data.len();
300 let offset = WIDTH_HEADER
301 .checked_add(
302 index
303 .checked_mul(WIDTH_PUBKEY)
304 .expect("removal index bound by data length"),
305 )
306 .expect("removal offset bound by data length");
307 let slot_end = offset
308 .checked_add(WIDTH_PUBKEY)
309 .expect("removal slot bound by data length");
310
311 data.copy_within(slot_end..len, offset);
312
313 let updated_length = len
314 .checked_sub(WIDTH_PUBKEY)
315 .expect("a solver was removed, so the length doesn't underflow");
316 Ok(updated_length)
317 }
318}
319
320#[cfg(any(test, feature = "test-fixtures"))]
323pub mod fixtures {
324 use proptest::prelude::*;
325 use solana_pubkey::Pubkey;
326
327 use super::{StateAccount, StateInitArgs, WIDTH_HEADER};
328
329 pub fn state_account_bytes(header: &StateInitArgs, solvers: &[Pubkey]) -> Vec<u8> {
333 let mut sorted = solvers.to_vec();
334 sorted.sort();
335 let mut bytes = vec![0u8; WIDTH_HEADER];
336 StateAccount::initialize(&mut bytes[..], header).expect("header fits");
337 for solver in &sorted {
338 bytes.extend_from_slice(&solver.to_bytes());
339 }
340 bytes
341 }
342
343 pub fn arb_init_params() -> impl Strategy<Value = StateInitArgs> {
345 (any::<[u8; 32]>(), any::<[u8; 32]>(), any::<[u8; 32]>()).prop_map(
346 |(manager, reclaim_authority, self_order_authority)| StateInitArgs {
347 manager: Pubkey::new_from_array(manager),
348 reclaim_authority: Pubkey::new_from_array(reclaim_authority),
349 self_order_authority: Pubkey::new_from_array(self_order_authority),
350 },
351 )
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use std::sync::LazyLock;
358
359 use super::*;
360 use crate::fixtures::pubkey_from_seed;
361
362 const DISCRIMINATOR_OFFSET: usize = 0;
364
365 static SAMPLE_INIT_ARGS: LazyLock<StateInitArgs> = LazyLock::new(|| StateInitArgs {
366 manager: pubkey_from_seed("SAMPLE_INIT_ARGS's sample manager"),
367 reclaim_authority: pubkey_from_seed("SAMPLE_INIT_ARGS's sample reclaim authority"),
368 self_order_authority: pubkey_from_seed("SAMPLE_INIT_ARGS's sample self-order authority"),
369 });
370
371 fn header_bytes() -> [u8; WIDTH_HEADER] {
373 let mut bytes = [0u8; WIDTH_HEADER];
374 StateAccount::initialize(&mut bytes[..], &SAMPLE_INIT_ARGS).expect("header fits");
375 bytes
376 }
377
378 fn state_bytes(solvers: &[Pubkey]) -> Vec<u8> {
381 super::fixtures::state_account_bytes(&SAMPLE_INIT_ARGS, solvers)
382 }
383
384 #[test]
385 fn header_has_the_canonical_wire_layout() {
386 assert_eq!(WIDTH_HEADER, 97);
387
388 let bytes = header_bytes();
389 assert_eq!(bytes[0], SettlementAccount::SettlementState.discriminator());
390 assert_eq!(&bytes[1..33], &SAMPLE_INIT_ARGS.manager.to_bytes()[..]);
391 assert_eq!(
392 &bytes[33..65],
393 &SAMPLE_INIT_ARGS.reclaim_authority.to_bytes()[..]
394 );
395 assert_eq!(
396 &bytes[65..97],
397 &SAMPLE_INIT_ARGS.self_order_authority.to_bytes()[..]
398 );
399 }
400
401 #[test]
402 fn reads_role_holders_from_the_header() {
403 let bytes = header_bytes();
404 let state = StateAccount::attach(&bytes[..]).expect("valid header");
405 assert_eq!(state.authority(Role::Manager), SAMPLE_INIT_ARGS.manager);
406 assert_eq!(
407 state.authority(Role::ReclaimAuthority),
408 SAMPLE_INIT_ARGS.reclaim_authority
409 );
410 assert_eq!(
411 state.authority(Role::SelfOrderAuthority),
412 SAMPLE_INIT_ARGS.self_order_authority
413 );
414 }
415
416 #[test]
417 fn new_rejects_wrong_discriminator() {
418 let mut bytes = header_bytes();
419 bytes[DISCRIMINATOR_OFFSET] = 0xff;
420 assert_eq!(
421 StateAccount::attach(&bytes[..]).err(),
422 Some(ProgramError::InvalidAccountData),
423 );
424 }
425
426 #[test]
427 fn new_rejects_too_short_buffer() {
428 let bytes = header_bytes();
429 assert_eq!(
430 StateAccount::attach(&bytes[..WIDTH_HEADER - 1]).err(),
431 Some(ProgramError::InvalidAccountData),
432 );
433 }
434
435 fn assert_set_authority_updates_only(target: Role) {
438 let new_holder = pubkey_from_seed("set_authority's new holder");
439
440 let mut bytes = header_bytes();
441 let before = Role::ALL.map(|role| {
442 StateAccount::attach(&bytes[..])
443 .expect("valid header")
444 .authority(role)
445 });
446
447 StateAccount::attach(&mut bytes[..])
448 .expect("valid header")
449 .set_authority(target, &new_holder);
450
451 let state = StateAccount::attach(&bytes[..]).expect("valid header");
452 for (role, prior) in Role::ALL.into_iter().zip(before) {
453 let expected = if role == target { new_holder } else { prior };
454 assert_eq!(state.authority(role), expected);
455 }
456 }
457
458 macro_rules! set_authority_test {
460 ($name:ident: $role:expr) => {
461 #[test]
462 fn $name() {
463 assert_set_authority_updates_only($role);
464 }
465 };
466 }
467
468 set_authority_test!(set_authority_updates_only_manager: Role::Manager);
469 set_authority_test!(set_authority_updates_only_reclaim_authority: Role::ReclaimAuthority);
470 set_authority_test!(
471 set_authority_updates_only_self_order_authority: Role::SelfOrderAuthority
472 );
473
474 #[test]
475 fn new_accepts_a_longer_account_and_reads_the_header() {
476 let mut bytes = header_bytes().to_vec();
477 bytes.push(0x42);
478
479 let state = StateAccount::attach(&bytes[..]).expect("header with trailing bytes is valid");
480 assert_eq!(state.authority(Role::Manager), SAMPLE_INIT_ARGS.manager);
481 assert_eq!(
482 state.authority(Role::ReclaimAuthority),
483 SAMPLE_INIT_ARGS.reclaim_authority
484 );
485 assert_eq!(
486 state.authority(Role::SelfOrderAuthority),
487 SAMPLE_INIT_ARGS.self_order_authority
488 );
489 }
490
491 fn sample_solvers() -> (Vec<u8>, [Pubkey; 3]) {
495 let solvers = [
496 pubkey_from_seed("sample_solver's solver a"),
497 pubkey_from_seed("sample_solver's solver b"),
498 pubkey_from_seed("sample_solver's solver c"),
499 ];
500 let bytes = state_bytes(&solvers);
501 let mut sorted = solvers;
502 sorted.sort();
503 (bytes, sorted)
504 }
505
506 #[test]
507 fn solvers_is_empty_without_any_stored() {
508 let bytes = state_bytes(&[]);
509 let state = StateAccount::attach(&bytes[..]).expect("valid header");
510 assert_eq!(state.solvers().count(), 0);
511 }
512
513 #[test]
514 fn solvers_lists_stored_solvers_sorted() {
515 let (bytes, sorted) = sample_solvers();
516 let state = StateAccount::attach(&bytes[..]).expect("valid header");
517 assert_eq!(state.solvers().collect::<Vec<_>>(), sorted);
518 }
519
520 #[test]
521 fn solver_search_on_empty_list() {
522 let bytes = state_bytes(&[]);
523 let state = StateAccount::attach(&bytes[..]).expect("valid header");
524 let absent = pubkey_from_seed("absent solver");
525 assert_eq!(state.solver_search(&absent), Err(0));
526 }
527
528 #[test]
529 fn solver_search_finds_present() {
530 let (bytes, sorted) = sample_solvers();
531 let state = StateAccount::attach(&bytes[..]).expect("valid header");
532 for (index, solver) in sorted.iter().enumerate() {
533 assert_eq!(state.solver_search(solver), Ok(index));
534 }
535 }
536
537 #[test]
538 fn solver_search_locates_absent() {
539 let (bytes, sorted) = sample_solvers();
540 let state = StateAccount::attach(&bytes[..]).expect("valid header");
541 let absent = pubkey_from_seed("absent solver");
544 let slot = state.solver_search(&absent).expect_err("absent solver");
545 assert_eq!(slot, sorted.partition_point(|s| s < &absent));
546 }
547
548 #[test]
549 fn initialize_rejects_too_small_buffer() {
550 let mut bytes = [0u8; WIDTH_HEADER - 1];
551 assert_eq!(
552 StateAccount::initialize(&mut bytes[..], &SAMPLE_INIT_ARGS).err(),
553 Some(ProgramError::AccountDataTooSmall),
554 );
555 }
556
557 mod proptest {
558 use ::proptest::prelude::*;
559
560 use super::*;
561
562 proptest! {
563 #[test]
566 fn is_solver_reflects_membership(
567 header in fixtures::arb_init_params(),
568 raw_solvers in prop::collection::btree_set(any::<[u8; 32]>(), 0..50),
570 raw_absent in any::<[u8; 32]>(),
571 ) {
572 prop_assume!(!raw_solvers.contains(&raw_absent));
573 let stored: Vec<Pubkey> =
574 raw_solvers.into_iter().map(Pubkey::new_from_array).collect();
575 let absent = Pubkey::new_from_array(raw_absent);
576
577 let bytes = fixtures::state_account_bytes(&header, &stored);
578 let state = StateAccount::attach(&bytes[..]).expect("valid header");
579
580 for solver in &stored {
581 prop_assert!(state.is_solver(solver));
582 }
583 prop_assert!(!state.is_solver(&absent));
584 }
585
586 #[test]
589 fn account_encode_roundtrip(header in fixtures::arb_init_params()) {
590 let mut bytes = [0u8; WIDTH_HEADER];
591 StateAccount::initialize(&mut bytes[..], &header).expect("header fits");
592
593 let state = StateAccount::attach(&bytes[..]).expect("valid header");
594 let StateInitArgs { manager, reclaim_authority, self_order_authority } = header;
595 prop_assert_eq!(state.authority(Role::Manager), manager);
596 prop_assert_eq!(state.authority(Role::ReclaimAuthority), reclaim_authority);
597 prop_assert_eq!(
598 state.authority(Role::SelfOrderAuthority),
599 self_order_authority
600 );
601 }
602
603 #[test]
604 fn insert_solver_inserts_an_absent_solver(
605 header in fixtures::arb_init_params(),
606 raw_solvers in prop::collection::btree_set(any::<[u8; 32]>(), 0..50),
608 raw_new in any::<[u8; 32]>(),
609 ) {
610 prop_assume!(!raw_solvers.contains(&raw_new));
611 let stored: Vec<Pubkey> =
612 raw_solvers.into_iter().map(Pubkey::new_from_array).collect();
613 let new = Pubkey::new_from_array(raw_new);
614
615 let mut bytes = fixtures::state_account_bytes(&header, &stored);
618 let grown_len = StateAccount::attach(&bytes[..])
619 .expect("valid header")
620 .grown_len()
621 .expect("grown length fits");
622 prop_assert_eq!(grown_len, bytes.len().strict_add(WIDTH_PUBKEY));
623 bytes.resize(grown_len, 0);
624 StateAccount::attach(&mut bytes[..])
625 .expect("valid header")
626 .insert_solver(&new)
627 .expect("absent solver inserts");
628
629 let mut expected = stored;
630 expected.push(new);
631 expected.sort();
632 let state = StateAccount::attach(&bytes[..]).expect("valid header");
633 prop_assert_eq!(state.solvers().collect::<Vec<_>>(), expected);
634 }
635
636 #[test]
637 fn insert_solver_rejects_an_existing_solver(
638 header in fixtures::arb_init_params(),
639 raw_solvers in prop::collection::btree_set(any::<[u8; 32]>(), 1..50),
641 pick in any::<prop::sample::Index>(),
642 ) {
643 let stored: Vec<Pubkey> =
644 raw_solvers.into_iter().map(Pubkey::new_from_array).collect();
645 let existing = stored[pick.index(stored.len())];
646
647 let mut bytes = fixtures::state_account_bytes(&header, &stored);
649 bytes.resize(bytes.len().strict_add(WIDTH_PUBKEY), 0);
650 prop_assert_eq!(
651 StateAccount::attach(&mut bytes[..])
652 .expect("valid header")
653 .insert_solver(&existing),
654 Err(SettlementError::SolverAlreadyExists.into()),
655 );
656
657 let state = StateAccount::attach(&bytes[..]).expect("valid header");
660 prop_assert_eq!(state.solvers().take(stored.len()).collect::<Vec<_>>(), stored);
661 }
662
663 #[test]
664 fn remove_solver_drops_a_present_solver(
665 header in fixtures::arb_init_params(),
666 raw_solvers in prop::collection::btree_set(any::<[u8; 32]>(), 1..50),
668 pick in any::<prop::sample::Index>(),
669 ) {
670 let stored: Vec<Pubkey> =
671 raw_solvers.into_iter().map(Pubkey::new_from_array).collect();
672 let index = pick.index(stored.len());
673 let removed = stored[index];
674
675 let mut bytes = fixtures::state_account_bytes(&header, &stored);
678 let shrunk_len = StateAccount::attach(&mut bytes[..])
679 .expect("valid header")
680 .remove_solver(&removed)
681 .expect("a present solver is removed");
682 prop_assert_eq!(shrunk_len, bytes.len().strict_sub(WIDTH_PUBKEY));
683 bytes.truncate(shrunk_len);
684
685 let mut expected = stored;
686 expected.remove(index);
687 let state = StateAccount::attach(&bytes[..]).expect("valid header");
688 prop_assert_eq!(state.solvers().collect::<Vec<_>>(), expected);
689 prop_assert_eq!(state.solver_search(&removed), Err(index));
690 }
691
692 #[test]
693 fn remove_solver_rejects_an_absent_solver(
694 header in fixtures::arb_init_params(),
695 raw_solvers in prop::collection::btree_set(any::<[u8; 32]>(), 0..50),
697 raw_absent in any::<[u8; 32]>(),
698 ) {
699 prop_assume!(!raw_solvers.contains(&raw_absent));
700 let stored: Vec<Pubkey> =
701 raw_solvers.into_iter().map(Pubkey::new_from_array).collect();
702 let absent = Pubkey::new_from_array(raw_absent);
703
704 let mut bytes = fixtures::state_account_bytes(&header, &stored);
705 prop_assert_eq!(
706 StateAccount::attach(&mut bytes[..])
707 .expect("valid header")
708 .remove_solver(&absent),
709 Err(SettlementError::SolverNotFound.into()),
710 );
711
712 let state = StateAccount::attach(&bytes[..]).expect("valid header");
714 prop_assert_eq!(state.solvers().collect::<Vec<_>>(), stored);
715 }
716 }
717 }
718}