1use crate::error::ProgramError;
2use crate::{AccountView, Address, ProgramResult};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub struct DuplicateAccount {
7 pub first_index: usize,
8 pub second_index: usize,
9 pub address: Address,
10}
11
12#[derive(Clone, Copy)]
14pub struct AccountAudit<'a> {
15 accounts: &'a [AccountView],
16}
17
18impl<'a> AccountAudit<'a> {
19 #[inline(always)]
20 pub const fn new(accounts: &'a [AccountView]) -> Self {
21 Self { accounts }
22 }
23
24 #[inline(always)]
25 pub fn accounts(&self) -> &'a [AccountView] {
26 self.accounts
27 }
28
29 #[inline]
30 pub fn first_duplicate(&self) -> Option<DuplicateAccount> {
31 self.first_duplicate_where(|_, _| true)
32 }
33
34 #[inline]
35 pub fn first_duplicate_writable(&self) -> Option<DuplicateAccount> {
36 self.first_duplicate_where(|left, right| left.is_writable() || right.is_writable())
37 }
38
39 #[inline]
40 pub fn first_duplicate_signer(&self) -> Option<DuplicateAccount> {
41 self.first_duplicate_where(|left, right| left.is_signer() || right.is_signer())
42 }
43
44 #[inline]
45 pub fn require_all_unique(&self) -> ProgramResult {
46 if self.first_duplicate().is_some() {
47 Err(ProgramError::InvalidArgument)
48 } else {
49 Ok(())
50 }
51 }
52
53 #[inline]
54 pub fn require_unique_writable(&self) -> ProgramResult {
55 if self.first_duplicate_writable().is_some() {
56 Err(ProgramError::InvalidArgument)
57 } else {
58 Ok(())
59 }
60 }
61
62 #[inline]
63 pub fn require_unique_signers(&self) -> ProgramResult {
64 if self.first_duplicate_signer().is_some() {
65 Err(ProgramError::InvalidArgument)
66 } else {
67 Ok(())
68 }
69 }
70
71 #[inline]
72 fn first_duplicate_where(
73 &self,
74 predicate: impl Fn(&AccountView, &AccountView) -> bool,
75 ) -> Option<DuplicateAccount> {
76 let mut i = 0;
77 while i < self.accounts.len() {
78 let left = &self.accounts[i];
79 let mut j = i + 1;
80 while j < self.accounts.len() {
81 let right = &self.accounts[j];
82 if left.address() == right.address() && predicate(left, right) {
83 return Some(DuplicateAccount {
84 first_index: i,
85 second_index: j,
86 address: *left.address(),
87 });
88 }
89 j += 1;
90 }
91 i += 1;
92 }
93 None
94 }
95}
96
97#[cfg(all(test, feature = "hopper-native-backend"))]
98mod tests {
99 use super::*;
100
101 use hopper_native::{
102 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
103 };
104
105 fn make_account(
106 address_byte: u8,
107 is_signer: bool,
108 is_writable: bool,
109 ) -> (std::vec::Vec<u8>, AccountView) {
110 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + 16];
111 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
112 unsafe {
113 raw.write(RuntimeAccount {
114 borrow_state: NOT_BORROWED,
115 is_signer: is_signer as u8,
116 is_writable: is_writable as u8,
117 executable: 0,
118 resize_delta: 0,
119 address: NativeAddress::new_from_array([address_byte; 32]),
120 owner: NativeAddress::new_from_array([7; 32]),
121 lamports: 1,
122 data_len: 16,
123 });
124 }
125 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
126 (backing, AccountView::from_backend(backend))
127 }
128
129 #[test]
130 fn detects_any_duplicate() {
131 let (_a_backing, first) = make_account(1, false, false);
132 let (_b_backing, second) = make_account(1, false, false);
133 let accounts = [first, second];
134 let audit = AccountAudit::new(&accounts);
135
136 let duplicate = audit.first_duplicate().unwrap();
137 assert_eq!(duplicate.first_index, 0);
138 assert_eq!(duplicate.second_index, 1);
139 assert_eq!(duplicate.address, Address::new_from_array([1; 32]));
140 assert_eq!(audit.require_all_unique(), Err(ProgramError::InvalidArgument));
141 }
142
143 #[test]
144 fn read_only_duplicates_do_not_fail_writable_audit() {
145 let (_a_backing, first) = make_account(2, false, false);
146 let (_b_backing, second) = make_account(2, false, false);
147 let accounts = [first, second];
148 let audit = AccountAudit::new(&accounts);
149
150 assert!(audit.first_duplicate_writable().is_none());
151 assert_eq!(audit.require_unique_writable(), Ok(()));
152 }
153
154 #[test]
155 fn writable_duplicates_are_rejected() {
156 let (_a_backing, first) = make_account(3, false, true);
157 let (_b_backing, second) = make_account(3, false, false);
158 let accounts = [first, second];
159 let audit = AccountAudit::new(&accounts);
160
161 let duplicate = audit.first_duplicate_writable().unwrap();
162 assert_eq!(duplicate.address, Address::new_from_array([3; 32]));
163 assert_eq!(audit.require_unique_writable(), Err(ProgramError::InvalidArgument));
164 }
165
166 #[test]
167 fn signer_duplicates_are_rejected() {
168 let (_a_backing, first) = make_account(4, true, false);
169 let (_b_backing, second) = make_account(4, false, false);
170 let accounts = [first, second];
171 let audit = AccountAudit::new(&accounts);
172
173 let duplicate = audit.first_duplicate_signer().unwrap();
174 assert_eq!(duplicate.address, Address::new_from_array([4; 32]));
175 assert_eq!(audit.require_unique_signers(), Err(ProgramError::InvalidArgument));
176 }
177}