1use core::ops::Deref;
2use pinocchio::error::ProgramError;
3use solana_account_view::{AccountView, Ref};
4
5pub use pinocchio_token_2022::instructions;
6
7use pinocchio_token_2022::state::TokenAccount as T22TokenAccount;
8
9const EXTENSION_TYPE_LEN: usize = 2;
10const EXTENSION_LENGTH_LEN: usize = 2;
11
12const T22_ACCOUNT_TYPE_MINT: u8 = 1;
14const T22_ACCOUNT_TYPE_TOKEN_ACCOUNT: u8 = 2;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum ExtensionType {
19 TransferFeeConfig,
20 MintCloseAuthority,
21 ConfidentialTransferMint,
22 PermanentDelegate,
23 TransferHook,
24 ConfidentialTransferFeeConfig,
25 MetadataPointer,
26 TokenMetadata,
27 GroupPointer,
28 TokenGroup,
29}
30
31impl ExtensionType {
32 fn from_bytes(b: [u8; 2]) -> Option<Self> {
33 match u16::from_le_bytes(b) {
34 1 => Some(Self::TransferFeeConfig),
35 3 => Some(Self::MintCloseAuthority),
36 4 => Some(Self::ConfidentialTransferMint),
37 12 => Some(Self::PermanentDelegate),
38 14 => Some(Self::TransferHook),
39 16 => Some(Self::ConfidentialTransferFeeConfig),
40 18 => Some(Self::MetadataPointer),
41 19 => Some(Self::TokenMetadata),
42 20 => Some(Self::GroupPointer),
43 21 => Some(Self::TokenGroup),
44 _ => None,
45 }
46 }
47}
48
49pub struct TokenAccount<'info>(Ref<'info, T22TokenAccount>);
50
51pub const MULTISIG_ACCOUNT_LENGTH: usize = 355;
53
54pub fn get_account_type(account_view: &AccountView) -> Result<u8, ProgramError> {
56 let data = account_view.try_borrow()?;
57 let account_type = data[T22TokenAccount::BASE_LEN];
59 Ok(account_type)
60}
61
62impl<'info> TokenAccount<'info> {
63 pub fn from_account_view(account_view: &'info AccountView) -> Result<Self, ProgramError> {
64 if account_view.owned_by(&pinocchio_token_2022::ID) {
65 let data_len = account_view.data_len();
66 if data_len > T22TokenAccount::BASE_LEN {
67 let account_type = get_account_type(account_view)?;
68 if account_type != T22_ACCOUNT_TYPE_TOKEN_ACCOUNT {
70 return Err(ProgramError::InvalidAccountData);
71 }
72 if data_len == MULTISIG_ACCOUNT_LENGTH {
74 return Err(ProgramError::InvalidAccountData);
75 }
76 }
77 T22TokenAccount::from_account_view(account_view)
78 .map(TokenAccount)
79 .map_err(|_| ProgramError::InvalidAccountData)
80 } else if account_view.owned_by(&pinocchio_token::ID) {
81 if account_view.data_len() != pinocchio_token::state::TokenAccount::LEN {
82 return Err(ProgramError::InvalidAccountData);
83 }
84 Ok(TokenAccount(Ref::map(
86 account_view.try_borrow()?,
87 |data| unsafe { T22TokenAccount::from_bytes_unchecked(data) },
88 )))
89 } else {
90 Err(ProgramError::InvalidAccountData)
91 }
92 }
93}
94
95impl Deref for TokenAccount<'_> {
96 type Target = T22TokenAccount;
97
98 fn deref(&self) -> &Self::Target {
99 &self.0
100 }
101}
102
103pub struct Mint<'info>(Ref<'info, pinocchio_token_2022::state::Mint>);
104
105impl<'info> Mint<'info> {
106 pub fn from_account_view(account_view: &'info AccountView) -> Result<Self, ProgramError> {
107 if account_view.owned_by(&pinocchio_token_2022::ID) {
108 let data_len = account_view.data_len();
109 let mint_base_len = pinocchio_token_2022::state::Mint::BASE_LEN;
110 if data_len > T22TokenAccount::BASE_LEN {
113 let account_type = get_account_type(account_view)?;
114 if account_type != T22_ACCOUNT_TYPE_MINT {
116 return Err(ProgramError::InvalidAccountData);
117 }
118 if data_len == MULTISIG_ACCOUNT_LENGTH {
120 return Err(ProgramError::InvalidAccountData);
121 }
122 } else if data_len != mint_base_len {
123 return Err(ProgramError::InvalidAccountData);
125 }
126 pinocchio_token_2022::state::Mint::from_account_view(account_view)
127 .map(Mint)
128 .map_err(|_| ProgramError::InvalidAccountData)
129 } else if account_view.owned_by(&pinocchio_token::ID) {
130 if account_view.data_len() != pinocchio_token::state::Mint::LEN {
131 return Err(ProgramError::InvalidAccountData);
132 }
133 Ok(Mint(Ref::map(account_view.try_borrow()?, |data| unsafe {
135 pinocchio_token_2022::state::Mint::from_bytes_unchecked(data)
136 })))
137 } else {
138 Err(ProgramError::InvalidAccountData)
139 }
140 }
141}
142
143impl Deref for Mint<'_> {
144 type Target = pinocchio_token_2022::state::Mint;
145
146 fn deref(&self) -> &Self::Target {
147 &self.0
148 }
149}
150
151pub fn get_all_extensions(acc_data_bytes: &[u8]) -> Result<Vec<ExtensionType>, ProgramError> {
154 let ext_start = T22TokenAccount::BASE_LEN + 1;
155 if acc_data_bytes.len() <= ext_start {
156 return Ok(Vec::new());
157 }
158 let account_type_byte = acc_data_bytes[T22TokenAccount::BASE_LEN];
159 if account_type_byte != T22_ACCOUNT_TYPE_MINT
160 && account_type_byte != T22_ACCOUNT_TYPE_TOKEN_ACCOUNT
161 {
162 return Err(ProgramError::InvalidAccountData);
163 }
164 let ext_bytes = &acc_data_bytes[ext_start..];
165 let mut extension_types = Vec::new();
166 let mut start = 0;
167 while start + EXTENSION_TYPE_LEN + EXTENSION_LENGTH_LEN <= ext_bytes.len() {
168 let type_bytes: [u8; 2] = ext_bytes[start..start + EXTENSION_TYPE_LEN]
169 .try_into()
170 .map_err(|_| ProgramError::InvalidAccountData)?;
171 let ext_type =
172 ExtensionType::from_bytes(type_bytes).ok_or(ProgramError::InvalidAccountData)?;
173 let len_bytes: [u8; 2] = ext_bytes
174 [start + EXTENSION_TYPE_LEN..start + EXTENSION_TYPE_LEN + EXTENSION_LENGTH_LEN]
175 .try_into()
176 .map_err(|_| ProgramError::InvalidAccountData)?;
177 let ext_len = u16::from_le_bytes(len_bytes) as usize;
178 extension_types.push(ext_type);
179 start += EXTENSION_TYPE_LEN + EXTENSION_LENGTH_LEN + ext_len;
180 }
181 Ok(extension_types)
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use solana_account_view::{AccountView, RuntimeAccount, NOT_BORROWED};
188
189 fn make_account(owner: [u8; 32], data: Vec<u8>) -> (Vec<u8>, AccountView) {
190 let header = core::mem::size_of::<RuntimeAccount>();
191 debug_assert_eq!(header, 88);
192 let mut buf = vec![0u8; header + data.len()];
193 buf[0] = NOT_BORROWED;
194 buf[40..72].copy_from_slice(&owner);
195 buf[80..88].copy_from_slice(&(data.len() as u64).to_le_bytes());
196 if !data.is_empty() {
197 buf[header..].copy_from_slice(&data);
198 }
199 let raw = buf.as_mut_ptr() as *mut RuntimeAccount;
200 let view = unsafe { AccountView::new_unchecked(raw) };
202 (buf, view)
203 }
204
205 fn t22_id() -> [u8; 32] {
206 unsafe { core::mem::transmute(pinocchio_token_2022::ID) }
208 }
209
210 fn token_id() -> [u8; 32] {
211 unsafe { core::mem::transmute(pinocchio_token::ID) }
213 }
214
215 #[test]
218 fn token_account_t22_base_len() {
219 let data = vec![0u8; T22TokenAccount::BASE_LEN];
220 let (_buf, view) = make_account(t22_id(), data);
221 assert!(TokenAccount::from_account_view(&view).is_ok());
222 }
223
224 #[test]
225 fn token_account_t22_with_extensions() {
226 let mut data = vec![0u8; T22TokenAccount::BASE_LEN + 2];
227 data[T22TokenAccount::BASE_LEN] = T22_ACCOUNT_TYPE_TOKEN_ACCOUNT;
228 let (_buf, view) = make_account(t22_id(), data);
229 assert!(TokenAccount::from_account_view(&view).is_ok());
230 }
231
232 #[test]
233 fn token_account_t22_wrong_type() {
234 let mut data = vec![0u8; T22TokenAccount::BASE_LEN + 2];
235 data[T22TokenAccount::BASE_LEN] = T22_ACCOUNT_TYPE_MINT; let (_buf, view) = make_account(t22_id(), data);
237 assert_eq!(
238 TokenAccount::from_account_view(&view).err().unwrap(),
239 ProgramError::InvalidAccountData,
240 );
241 }
242
243 #[test]
244 fn token_account_t22_multisig_length() {
245 let mut data = vec![0u8; MULTISIG_ACCOUNT_LENGTH];
246 data[T22TokenAccount::BASE_LEN] = T22_ACCOUNT_TYPE_TOKEN_ACCOUNT;
247 let (_buf, view) = make_account(t22_id(), data);
248 assert_eq!(
249 TokenAccount::from_account_view(&view).err().unwrap(),
250 ProgramError::InvalidAccountData,
251 );
252 }
253
254 #[test]
255 fn token_account_legacy_success() {
256 let data = vec![0u8; pinocchio_token::state::TokenAccount::LEN];
257 let (_buf, view) = make_account(token_id(), data);
258 assert!(TokenAccount::from_account_view(&view).is_ok());
259 }
260
261 #[test]
262 fn token_account_legacy_wrong_length() {
263 let data = vec![0u8; 100];
264 let (_buf, view) = make_account(token_id(), data);
265 assert_eq!(
266 TokenAccount::from_account_view(&view).err().unwrap(),
267 ProgramError::InvalidAccountData,
268 );
269 }
270
271 #[test]
272 fn token_account_wrong_owner() {
273 let data = vec![0u8; T22TokenAccount::BASE_LEN];
274 let (_buf, view) = make_account([0u8; 32], data);
275 assert_eq!(
276 TokenAccount::from_account_view(&view).err().unwrap(),
277 ProgramError::InvalidAccountData,
278 );
279 }
280
281 #[test]
284 fn mint_t22_base_len() {
285 let data = vec![0u8; pinocchio_token_2022::state::Mint::BASE_LEN];
286 let (_buf, view) = make_account(t22_id(), data);
287 assert!(Mint::from_account_view(&view).is_ok());
288 }
289
290 #[test]
291 fn mint_t22_with_extensions() {
292 let mut data = vec![0u8; T22TokenAccount::BASE_LEN + 2];
293 data[T22TokenAccount::BASE_LEN] = T22_ACCOUNT_TYPE_MINT;
294 let (_buf, view) = make_account(t22_id(), data);
295 assert!(Mint::from_account_view(&view).is_ok());
296 }
297
298 #[test]
299 fn mint_t22_wrong_type() {
300 let mut data = vec![0u8; T22TokenAccount::BASE_LEN + 2];
301 data[T22TokenAccount::BASE_LEN] = T22_ACCOUNT_TYPE_TOKEN_ACCOUNT; let (_buf, view) = make_account(t22_id(), data);
303 assert_eq!(
304 Mint::from_account_view(&view).err().unwrap(),
305 ProgramError::InvalidAccountData,
306 );
307 }
308
309 #[test]
310 fn mint_t22_rejects_token_account_as_mint() {
311 let data = vec![0u8; T22TokenAccount::BASE_LEN];
312 let (_buf, view) = make_account(t22_id(), data);
313
314 assert!(TokenAccount::from_account_view(&view).is_ok());
316
317 assert_eq!(
320 Mint::from_account_view(&view).err().unwrap(),
321 ProgramError::InvalidAccountData,
322 );
323 }
324
325 #[test]
326 fn mint_t22_invalid_intermediate_length_rejected() {
327 let data = vec![0u8; 100];
328 let (_buf, view) = make_account(t22_id(), data);
329 assert_eq!(
330 Mint::from_account_view(&view).err().unwrap(),
331 ProgramError::InvalidAccountData,
332 );
333 }
334
335 #[test]
336 fn mint_legacy_success() {
337 let data = vec![0u8; pinocchio_token::state::Mint::LEN];
338 let (_buf, view) = make_account(token_id(), data);
339 assert!(Mint::from_account_view(&view).is_ok());
340 }
341
342 #[test]
343 fn mint_legacy_wrong_length() {
344 let data = vec![0u8; 100];
345 let (_buf, view) = make_account(token_id(), data);
346 assert_eq!(
347 Mint::from_account_view(&view).err().unwrap(),
348 ProgramError::InvalidAccountData,
349 );
350 }
351
352 #[test]
353 fn mint_wrong_owner() {
354 let data = vec![0u8; pinocchio_token_2022::state::Mint::BASE_LEN];
355 let (_buf, view) = make_account([0u8; 32], data);
356 assert_eq!(
357 Mint::from_account_view(&view).err().unwrap(),
358 ProgramError::InvalidAccountData,
359 );
360 }
361
362 pub const TEST_MINT_WITH_EXTENSIONS_SLICE: &[u8] = &[
365 1, 0, 0, 0, 221, 76, 72, 108, 144, 248, 182, 240, 7, 195, 4, 239, 36, 129, 248, 5, 24, 107,
366 232, 253, 95, 82, 172, 209, 2, 92, 183, 155, 159, 103, 255, 33, 133, 204, 6, 44, 35, 140,
367 0, 0, 6, 1, 1, 0, 0, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173,
368 49, 41, 63, 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43, 0, 0, 0, 0, 0, 0,
369 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
370 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
371 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
372 3, 0, 32, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41, 63,
374 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43,
375 12, 0, 32, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41,
377 63, 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43,
378 1, 0, 108, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41,
380 63, 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43, 23, 133, 50, 97, 239, 106,
381 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41, 63, 207, 7, 207, 18, 10, 181, 185, 161,
382 87, 6, 84, 141, 192, 43, 0, 0, 0, 0, 0, 0, 0, 0, 93, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
383 0, 0, 0, 0, 93, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
384 4, 0, 65, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41, 63,
386 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
387 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
388 16, 0, 129, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41,
390 63, 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43, 28, 55, 230, 67, 59, 115,
391 4, 221, 130, 115, 122, 228, 13, 155, 139, 243, 196, 159, 91, 14, 108, 73, 168, 213, 51, 40,
392 179, 229, 6, 144, 28, 87, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
393 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
394 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
395 14, 0, 64, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41,
397 63, 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0,
398 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
399 18, 0, 64, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41,
401 63, 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43, 23, 146, 72, 59, 108, 138,
402 42, 135, 183, 71, 29, 129, 79, 149, 145, 249, 57, 92, 132, 10, 156, 227, 217, 244, 213,
403 186, 125, 58, 75, 138, 116, 158,
404 19, 0, 174, 0, 23, 133, 50, 97, 239, 106, 184, 83, 42, 103, 240, 83, 134, 90, 173, 49, 41,
406 63, 207, 7, 207, 18, 10, 181, 185, 161, 87, 6, 84, 141, 192, 43, 23, 146, 72, 59, 108, 138,
407 42, 135, 183, 71, 29, 129, 79, 149, 145, 249, 57, 92, 132, 10, 156, 227, 217, 244, 213,
408 186, 125, 58, 75, 138, 116, 158, 10, 0, 0, 0, 80, 97, 121, 80, 97, 108, 32, 85, 83, 68, 5,
409 0, 0, 0, 80, 89, 85, 83, 68, 79, 0, 0, 0, 104, 116, 116, 112, 115, 58, 47, 47, 116, 111,
410 107, 101, 110, 45, 109, 101, 116, 97, 100, 97, 116, 97, 46, 112, 97, 120, 111, 115, 46, 99,
411 111, 109, 47, 112, 121, 117, 115, 100, 95, 109, 101, 116, 97, 100, 97, 116, 97, 47, 112,
412 114, 111, 100, 47, 115, 111, 108, 97, 110, 97, 47, 112, 121, 117, 115, 100, 95, 109, 101,
413 116, 97, 100, 97, 116, 97, 46, 106, 115, 111, 110, 0, 0, 0, 0,
414 20, 0, 64, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
416 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
417 2, 2, 2, 2, 2, 2, 2, 2,
418 21, 0, 80, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
420 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
421 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
422 ];
423
424 #[test]
425 fn test_get_all_extensions_for_mint() {
426 let extension_types = get_all_extensions(TEST_MINT_WITH_EXTENSIONS_SLICE).unwrap();
427 assert_eq!(
428 extension_types,
429 vec![
430 ExtensionType::MintCloseAuthority,
431 ExtensionType::PermanentDelegate,
432 ExtensionType::TransferFeeConfig,
433 ExtensionType::ConfidentialTransferMint,
434 ExtensionType::ConfidentialTransferFeeConfig,
435 ExtensionType::TransferHook,
436 ExtensionType::MetadataPointer,
437 ExtensionType::TokenMetadata,
438 ExtensionType::GroupPointer,
439 ExtensionType::TokenGroup,
440 ]
441 );
442 }
443
444 #[test]
445 fn test_get_all_extensions_no_extensions() {
446 let data = vec![0u8; T22TokenAccount::BASE_LEN + 1];
447 assert_eq!(get_all_extensions(&data).unwrap(), vec![]);
448 }
449
450 #[test]
451 fn test_get_all_extensions_wrong_account_type() {
452 let base = T22TokenAccount::BASE_LEN;
453 let mut data = vec![0u8; base + 2];
454 data[base] = 0; assert_eq!(
456 get_all_extensions(&data).err().unwrap(),
457 ProgramError::InvalidAccountData,
458 );
459 }
460}