state.rs 17 KB

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