state.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. //! State transition types
  2. use {
  3. crate::instruction::MAX_SIGNERS,
  4. arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs},
  5. num_enum::TryFromPrimitive,
  6. solana_program_error::ProgramError,
  7. solana_program_option::COption,
  8. solana_program_pack::{IsInitialized, Pack, Sealed},
  9. solana_pubkey::{Pubkey, PUBKEY_BYTES},
  10. };
  11. /// Mint data.
  12. #[repr(C)]
  13. #[derive(Clone, Copy, Debug, Default, PartialEq)]
  14. pub struct Mint {
  15. /// Optional authority used to mint new tokens. The mint authority may only
  16. /// be provided during mint creation. If no mint authority is present
  17. /// then the mint has a fixed supply and no further tokens may be
  18. /// minted.
  19. pub mint_authority: COption<Pubkey>,
  20. /// Total supply of tokens.
  21. pub supply: u64,
  22. /// Number of base 10 digits to the right of the decimal place.
  23. pub decimals: u8,
  24. /// Is `true` if this structure has been initialized
  25. pub is_initialized: bool,
  26. /// Optional authority to freeze token accounts.
  27. pub freeze_authority: COption<Pubkey>,
  28. }
  29. impl Sealed for Mint {}
  30. impl IsInitialized for Mint {
  31. fn is_initialized(&self) -> bool {
  32. self.is_initialized
  33. }
  34. }
  35. impl Pack for Mint {
  36. const LEN: usize = 82;
  37. fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
  38. let src = array_ref![src, 0, 82];
  39. let (mint_authority, supply, decimals, is_initialized, freeze_authority) =
  40. array_refs![src, 36, 8, 1, 1, 36];
  41. let mint_authority = unpack_coption_key(mint_authority)?;
  42. let supply = u64::from_le_bytes(*supply);
  43. let decimals = decimals[0];
  44. let is_initialized = match is_initialized {
  45. [0] => false,
  46. [1] => true,
  47. _ => return Err(ProgramError::InvalidAccountData),
  48. };
  49. let freeze_authority = unpack_coption_key(freeze_authority)?;
  50. Ok(Mint {
  51. mint_authority,
  52. supply,
  53. decimals,
  54. is_initialized,
  55. freeze_authority,
  56. })
  57. }
  58. fn pack_into_slice(&self, dst: &mut [u8]) {
  59. let dst = array_mut_ref![dst, 0, 82];
  60. let (
  61. mint_authority_dst,
  62. supply_dst,
  63. decimals_dst,
  64. is_initialized_dst,
  65. freeze_authority_dst,
  66. ) = mut_array_refs![dst, 36, 8, 1, 1, 36];
  67. let &Mint {
  68. ref mint_authority,
  69. supply,
  70. decimals,
  71. is_initialized,
  72. ref freeze_authority,
  73. } = self;
  74. pack_coption_key(mint_authority, mint_authority_dst);
  75. *supply_dst = supply.to_le_bytes();
  76. decimals_dst[0] = decimals;
  77. is_initialized_dst[0] = is_initialized as u8;
  78. pack_coption_key(freeze_authority, freeze_authority_dst);
  79. }
  80. }
  81. /// Account data.
  82. #[repr(C)]
  83. #[derive(Clone, Copy, Debug, Default, PartialEq)]
  84. pub struct Account {
  85. /// The mint associated with this account
  86. pub mint: Pubkey,
  87. /// The owner of this account.
  88. pub owner: Pubkey,
  89. /// The amount of tokens this account holds.
  90. pub amount: u64,
  91. /// If `delegate` is `Some` then `delegated_amount` represents
  92. /// the amount authorized by the delegate
  93. pub delegate: COption<Pubkey>,
  94. /// The account's state
  95. pub state: AccountState,
  96. /// If `is_native.is_some`, this is a native token, and the value logs the
  97. /// rent-exempt reserve. An Account is required to be rent-exempt, so
  98. /// the value is used by the Processor to ensure that wrapped SOL
  99. /// accounts do not drop below this threshold.
  100. pub is_native: COption<u64>,
  101. /// The amount delegated
  102. pub delegated_amount: u64,
  103. /// Optional authority to close the account.
  104. pub close_authority: COption<Pubkey>,
  105. }
  106. impl Account {
  107. /// Checks if account is frozen
  108. pub fn is_frozen(&self) -> bool {
  109. self.state == AccountState::Frozen
  110. }
  111. /// Checks if account is native
  112. pub fn is_native(&self) -> bool {
  113. self.is_native.is_some()
  114. }
  115. /// Checks if a token Account's owner is the `system_program` or the
  116. /// incinerator
  117. pub fn is_owned_by_system_program_or_incinerator(&self) -> bool {
  118. solana_sdk_ids::system_program::check_id(&self.owner)
  119. || solana_sdk_ids::incinerator::check_id(&self.owner)
  120. }
  121. }
  122. impl Sealed for Account {}
  123. impl IsInitialized for Account {
  124. fn is_initialized(&self) -> bool {
  125. self.state != AccountState::Uninitialized
  126. }
  127. }
  128. impl Pack for Account {
  129. const LEN: usize = 165;
  130. fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
  131. let src = array_ref![src, 0, 165];
  132. let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) =
  133. array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36];
  134. Ok(Account {
  135. mint: Pubkey::new_from_array(*mint),
  136. owner: Pubkey::new_from_array(*owner),
  137. amount: u64::from_le_bytes(*amount),
  138. delegate: unpack_coption_key(delegate)?,
  139. state: AccountState::try_from_primitive(state[0])
  140. .or(Err(ProgramError::InvalidAccountData))?,
  141. is_native: unpack_coption_u64(is_native)?,
  142. delegated_amount: u64::from_le_bytes(*delegated_amount),
  143. close_authority: unpack_coption_key(close_authority)?,
  144. })
  145. }
  146. fn pack_into_slice(&self, dst: &mut [u8]) {
  147. let dst = array_mut_ref![dst, 0, 165];
  148. let (
  149. mint_dst,
  150. owner_dst,
  151. amount_dst,
  152. delegate_dst,
  153. state_dst,
  154. is_native_dst,
  155. delegated_amount_dst,
  156. close_authority_dst,
  157. ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36];
  158. let &Account {
  159. ref mint,
  160. ref owner,
  161. amount,
  162. ref delegate,
  163. state,
  164. ref is_native,
  165. delegated_amount,
  166. ref close_authority,
  167. } = self;
  168. mint_dst.copy_from_slice(mint.as_ref());
  169. owner_dst.copy_from_slice(owner.as_ref());
  170. *amount_dst = amount.to_le_bytes();
  171. pack_coption_key(delegate, delegate_dst);
  172. state_dst[0] = state as u8;
  173. pack_coption_u64(is_native, is_native_dst);
  174. *delegated_amount_dst = delegated_amount.to_le_bytes();
  175. pack_coption_key(close_authority, close_authority_dst);
  176. }
  177. }
  178. /// Account state.
  179. #[repr(u8)]
  180. #[derive(Clone, Copy, Debug, Default, PartialEq, TryFromPrimitive)]
  181. pub enum AccountState {
  182. /// Account is not yet initialized
  183. #[default]
  184. Uninitialized,
  185. /// Account is initialized; the account owner and/or delegate may perform
  186. /// permitted operations on this account
  187. Initialized,
  188. /// Account has been frozen by the mint freeze authority. Neither the
  189. /// account owner nor the delegate are able to perform operations on
  190. /// this account.
  191. Frozen,
  192. }
  193. /// Multisignature data.
  194. #[repr(C)]
  195. #[derive(Clone, Copy, Debug, Default, PartialEq)]
  196. pub struct Multisig {
  197. /// Number of signers required
  198. pub m: u8,
  199. /// Number of valid signers
  200. pub n: u8,
  201. /// Is `true` if this structure has been initialized
  202. pub is_initialized: bool,
  203. /// Signer public keys
  204. pub signers: [Pubkey; MAX_SIGNERS],
  205. }
  206. impl Sealed for Multisig {}
  207. impl IsInitialized for Multisig {
  208. fn is_initialized(&self) -> bool {
  209. self.is_initialized
  210. }
  211. }
  212. impl Pack for Multisig {
  213. const LEN: usize = 355;
  214. fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
  215. let src = array_ref![src, 0, 355];
  216. #[allow(clippy::ptr_offset_with_cast)]
  217. let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS];
  218. let mut result = Multisig {
  219. m: m[0],
  220. n: n[0],
  221. is_initialized: match is_initialized {
  222. [0] => false,
  223. [1] => true,
  224. _ => return Err(ProgramError::InvalidAccountData),
  225. },
  226. signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS],
  227. };
  228. for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
  229. *dst = Pubkey::try_from(src).map_err(|_| ProgramError::InvalidAccountData)?;
  230. }
  231. Ok(result)
  232. }
  233. fn pack_into_slice(&self, dst: &mut [u8]) {
  234. let dst = array_mut_ref![dst, 0, 355];
  235. #[allow(clippy::ptr_offset_with_cast)]
  236. let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
  237. *m = [self.m];
  238. *n = [self.n];
  239. *is_initialized = [self.is_initialized as u8];
  240. for (i, src) in self.signers.iter().enumerate() {
  241. let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
  242. dst_array.copy_from_slice(src.as_ref());
  243. }
  244. }
  245. }
  246. // Helpers
  247. fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
  248. let (tag, body) = mut_array_refs![dst, 4, 32];
  249. match src {
  250. COption::Some(key) => {
  251. *tag = [1, 0, 0, 0];
  252. body.copy_from_slice(key.as_ref());
  253. }
  254. COption::None => {
  255. *tag = [0; 4];
  256. }
  257. }
  258. }
  259. fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
  260. let (tag, body) = array_refs![src, 4, 32];
  261. match *tag {
  262. [0, 0, 0, 0] => Ok(COption::None),
  263. [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
  264. _ => Err(ProgramError::InvalidAccountData),
  265. }
  266. }
  267. fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
  268. let (tag, body) = mut_array_refs![dst, 4, 8];
  269. match src {
  270. COption::Some(amount) => {
  271. *tag = [1, 0, 0, 0];
  272. *body = amount.to_le_bytes();
  273. }
  274. COption::None => {
  275. *tag = [0; 4];
  276. }
  277. }
  278. }
  279. fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
  280. let (tag, body) = array_refs![src, 4, 8];
  281. match *tag {
  282. [0, 0, 0, 0] => Ok(COption::None),
  283. [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
  284. _ => Err(ProgramError::InvalidAccountData),
  285. }
  286. }
  287. const SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
  288. const SPL_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;
  289. /// A trait for token Account structs to enable efficiently unpacking various
  290. /// fields without unpacking the complete state.
  291. pub trait GenericTokenAccount {
  292. /// Check if the account data is a valid token account
  293. fn valid_account_data(account_data: &[u8]) -> bool;
  294. /// Call after account length has already been verified to unpack the
  295. /// account owner
  296. fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
  297. Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
  298. }
  299. /// Call after account length has already been verified to unpack the
  300. /// account mint
  301. fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
  302. Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
  303. }
  304. /// Call after account length has already been verified to unpack a Pubkey
  305. /// at the specified offset. Panics if `account_data.len()` is less than
  306. /// `PUBKEY_BYTES`
  307. fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
  308. bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
  309. }
  310. /// Unpacks an account's owner from opaque account data.
  311. fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
  312. if Self::valid_account_data(account_data) {
  313. Some(Self::unpack_account_owner_unchecked(account_data))
  314. } else {
  315. None
  316. }
  317. }
  318. /// Unpacks an account's mint from opaque account data.
  319. fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
  320. if Self::valid_account_data(account_data) {
  321. Some(Self::unpack_account_mint_unchecked(account_data))
  322. } else {
  323. None
  324. }
  325. }
  326. }
  327. /// The offset of state field in Account's C representation
  328. pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
  329. /// Check if the account data buffer represents an initialized account.
  330. /// This is checking the `state` (`AccountState`) field of an Account object.
  331. pub fn is_initialized_account(account_data: &[u8]) -> bool {
  332. *account_data
  333. .get(ACCOUNT_INITIALIZED_INDEX)
  334. .unwrap_or(&(AccountState::Uninitialized as u8))
  335. != AccountState::Uninitialized as u8
  336. }
  337. impl GenericTokenAccount for Account {
  338. fn valid_account_data(account_data: &[u8]) -> bool {
  339. account_data.len() == Account::LEN && is_initialized_account(account_data)
  340. }
  341. }
  342. #[cfg(test)]
  343. mod tests {
  344. use super::*;
  345. #[test]
  346. fn test_mint_unpack_from_slice() {
  347. let src: [u8; 82] = [0; 82];
  348. let mint = Mint::unpack_from_slice(&src).unwrap();
  349. assert!(!mint.is_initialized);
  350. let mut src: [u8; 82] = [0; 82];
  351. src[45] = 2;
  352. let mint = Mint::unpack_from_slice(&src).unwrap_err();
  353. assert_eq!(mint, ProgramError::InvalidAccountData);
  354. }
  355. #[test]
  356. fn test_account_state() {
  357. let account_state = AccountState::default();
  358. assert_eq!(account_state, AccountState::Uninitialized);
  359. }
  360. #[test]
  361. fn test_multisig_unpack_from_slice() {
  362. let src: [u8; 355] = [0; 355];
  363. let multisig = Multisig::unpack_from_slice(&src).unwrap();
  364. assert_eq!(multisig.m, 0);
  365. assert_eq!(multisig.n, 0);
  366. assert!(!multisig.is_initialized);
  367. let mut src: [u8; 355] = [0; 355];
  368. src[0] = 1;
  369. src[1] = 1;
  370. src[2] = 1;
  371. let multisig = Multisig::unpack_from_slice(&src).unwrap();
  372. assert_eq!(multisig.m, 1);
  373. assert_eq!(multisig.n, 1);
  374. assert!(multisig.is_initialized);
  375. let mut src: [u8; 355] = [0; 355];
  376. src[2] = 2;
  377. let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
  378. assert_eq!(multisig, ProgramError::InvalidAccountData);
  379. }
  380. #[test]
  381. fn test_unpack_coption_key() {
  382. let src: [u8; 36] = [0; 36];
  383. let result = unpack_coption_key(&src).unwrap();
  384. assert_eq!(result, COption::None);
  385. let mut src: [u8; 36] = [0; 36];
  386. src[1] = 1;
  387. let result = unpack_coption_key(&src).unwrap_err();
  388. assert_eq!(result, ProgramError::InvalidAccountData);
  389. }
  390. #[test]
  391. fn test_unpack_coption_u64() {
  392. let src: [u8; 12] = [0; 12];
  393. let result = unpack_coption_u64(&src).unwrap();
  394. assert_eq!(result, COption::None);
  395. let mut src: [u8; 12] = [0; 12];
  396. src[0] = 1;
  397. let result = unpack_coption_u64(&src).unwrap();
  398. assert_eq!(result, COption::Some(0));
  399. let mut src: [u8; 12] = [0; 12];
  400. src[1] = 1;
  401. let result = unpack_coption_u64(&src).unwrap_err();
  402. assert_eq!(result, ProgramError::InvalidAccountData);
  403. }
  404. #[test]
  405. fn test_unpack_token_owner() {
  406. // Account data length < Account::LEN, unpack will not return a key
  407. let src: [u8; 12] = [0; 12];
  408. let result = Account::unpack_account_owner(&src);
  409. assert_eq!(result, Option::None);
  410. // The right account data size and initialized, unpack will return some key
  411. let mut src: [u8; Account::LEN] = [0; Account::LEN];
  412. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
  413. let result = Account::unpack_account_owner(&src);
  414. assert!(result.is_some());
  415. // The right account data size and frozen, unpack will return some key
  416. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
  417. let result = Account::unpack_account_owner(&src);
  418. assert!(result.is_some());
  419. // The right account data size and uninitialized, unpack will return None
  420. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
  421. let result = Account::unpack_account_mint(&src);
  422. assert_eq!(result, Option::None);
  423. // Account data length > account data size, unpack will not return a key
  424. let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
  425. let result = Account::unpack_account_owner(&src);
  426. assert_eq!(result, Option::None);
  427. }
  428. #[test]
  429. fn test_unpack_token_mint() {
  430. // Account data length < Account::LEN, unpack will not return a key
  431. let src: [u8; 12] = [0; 12];
  432. let result = Account::unpack_account_mint(&src);
  433. assert_eq!(result, Option::None);
  434. // The right account data size and initialized, unpack will return some key
  435. let mut src: [u8; Account::LEN] = [0; Account::LEN];
  436. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
  437. let result = Account::unpack_account_mint(&src);
  438. assert!(result.is_some());
  439. // The right account data size and frozen, unpack will return some key
  440. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
  441. let result = Account::unpack_account_mint(&src);
  442. assert!(result.is_some());
  443. // The right account data size and uninitialized, unpack will return None
  444. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
  445. let result = Account::unpack_account_mint(&src);
  446. assert_eq!(result, Option::None);
  447. // Account data length > account data size, unpack will not return a key
  448. let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
  449. let result = Account::unpack_account_mint(&src);
  450. assert_eq!(result, Option::None);
  451. }
  452. }