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. }
  114. impl Sealed for Account {}
  115. impl IsInitialized for Account {
  116. fn is_initialized(&self) -> bool {
  117. self.state != AccountState::Uninitialized
  118. }
  119. }
  120. impl Pack for Account {
  121. const LEN: usize = 165;
  122. fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
  123. let src = array_ref![src, 0, 165];
  124. let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) =
  125. array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36];
  126. Ok(Account {
  127. mint: Pubkey::new_from_array(*mint),
  128. owner: Pubkey::new_from_array(*owner),
  129. amount: u64::from_le_bytes(*amount),
  130. delegate: unpack_coption_key(delegate)?,
  131. state: AccountState::try_from_primitive(state[0])
  132. .or(Err(ProgramError::InvalidAccountData))?,
  133. is_native: unpack_coption_u64(is_native)?,
  134. delegated_amount: u64::from_le_bytes(*delegated_amount),
  135. close_authority: unpack_coption_key(close_authority)?,
  136. })
  137. }
  138. fn pack_into_slice(&self, dst: &mut [u8]) {
  139. let dst = array_mut_ref![dst, 0, 165];
  140. let (
  141. mint_dst,
  142. owner_dst,
  143. amount_dst,
  144. delegate_dst,
  145. state_dst,
  146. is_native_dst,
  147. delegated_amount_dst,
  148. close_authority_dst,
  149. ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36];
  150. let &Account {
  151. ref mint,
  152. ref owner,
  153. amount,
  154. ref delegate,
  155. state,
  156. ref is_native,
  157. delegated_amount,
  158. ref close_authority,
  159. } = self;
  160. mint_dst.copy_from_slice(mint.as_ref());
  161. owner_dst.copy_from_slice(owner.as_ref());
  162. *amount_dst = amount.to_le_bytes();
  163. pack_coption_key(delegate, delegate_dst);
  164. state_dst[0] = state as u8;
  165. pack_coption_u64(is_native, is_native_dst);
  166. *delegated_amount_dst = delegated_amount.to_le_bytes();
  167. pack_coption_key(close_authority, close_authority_dst);
  168. }
  169. }
  170. /// Account state.
  171. #[repr(u8)]
  172. #[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
  173. pub enum AccountState {
  174. /// Account is not yet initialized
  175. Uninitialized,
  176. /// Account is initialized; the account owner and/or delegate may perform permitted operations
  177. /// on this account
  178. Initialized,
  179. /// Account has been frozen by the mint freeze authority. Neither the account owner nor
  180. /// the delegate are able to perform operations on this account.
  181. Frozen,
  182. }
  183. impl Default for AccountState {
  184. fn default() -> Self {
  185. AccountState::Uninitialized
  186. }
  187. }
  188. /// Multisignature data.
  189. #[repr(C)]
  190. #[derive(Clone, Copy, Debug, Default, PartialEq)]
  191. pub struct Multisig {
  192. /// Number of signers required
  193. pub m: u8,
  194. /// Number of valid signers
  195. pub n: u8,
  196. /// Is `true` if this structure has been initialized
  197. pub is_initialized: bool,
  198. /// Signer public keys
  199. pub signers: [Pubkey; MAX_SIGNERS],
  200. }
  201. impl Sealed for Multisig {}
  202. impl IsInitialized for Multisig {
  203. fn is_initialized(&self) -> bool {
  204. self.is_initialized
  205. }
  206. }
  207. impl Pack for Multisig {
  208. const LEN: usize = 355;
  209. fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
  210. let src = array_ref![src, 0, 355];
  211. #[allow(clippy::ptr_offset_with_cast)]
  212. let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS];
  213. let mut result = Multisig {
  214. m: m[0],
  215. n: n[0],
  216. is_initialized: match is_initialized {
  217. [0] => false,
  218. [1] => true,
  219. _ => return Err(ProgramError::InvalidAccountData),
  220. },
  221. signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS],
  222. };
  223. for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
  224. *dst = Pubkey::new(src);
  225. }
  226. Ok(result)
  227. }
  228. fn pack_into_slice(&self, dst: &mut [u8]) {
  229. let dst = array_mut_ref![dst, 0, 355];
  230. #[allow(clippy::ptr_offset_with_cast)]
  231. let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
  232. *m = [self.m];
  233. *n = [self.n];
  234. *is_initialized = [self.is_initialized as u8];
  235. for (i, src) in self.signers.iter().enumerate() {
  236. let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
  237. dst_array.copy_from_slice(src.as_ref());
  238. }
  239. }
  240. }
  241. // Helpers
  242. fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
  243. let (tag, body) = mut_array_refs![dst, 4, 32];
  244. match src {
  245. COption::Some(key) => {
  246. *tag = [1, 0, 0, 0];
  247. body.copy_from_slice(key.as_ref());
  248. }
  249. COption::None => {
  250. *tag = [0; 4];
  251. }
  252. }
  253. }
  254. fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
  255. let (tag, body) = array_refs![src, 4, 32];
  256. match *tag {
  257. [0, 0, 0, 0] => Ok(COption::None),
  258. [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
  259. _ => Err(ProgramError::InvalidAccountData),
  260. }
  261. }
  262. fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
  263. let (tag, body) = mut_array_refs![dst, 4, 8];
  264. match src {
  265. COption::Some(amount) => {
  266. *tag = [1, 0, 0, 0];
  267. *body = amount.to_le_bytes();
  268. }
  269. COption::None => {
  270. *tag = [0; 4];
  271. }
  272. }
  273. }
  274. fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
  275. let (tag, body) = array_refs![src, 4, 8];
  276. match *tag {
  277. [0, 0, 0, 0] => Ok(COption::None),
  278. [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
  279. _ => Err(ProgramError::InvalidAccountData),
  280. }
  281. }
  282. const SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
  283. const SPL_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;
  284. /// A trait for token Account structs to enable efficiently unpacking various fields
  285. /// without unpacking the complete state.
  286. pub trait GenericTokenAccount {
  287. /// Check if the account data is a valid token account
  288. fn valid_account_data(account_data: &[u8]) -> bool;
  289. /// Call after account length has already been verified to unpack the account owner
  290. fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
  291. Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
  292. }
  293. /// Call after account length has already been verified to unpack the account mint
  294. fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
  295. Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
  296. }
  297. /// Call after account length has already been verified to unpack a Pubkey at
  298. /// the specified offset. Panics if `account_data.len()` is less than `PUBKEY_BYTES`
  299. fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
  300. bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
  301. }
  302. /// Unpacks an account's owner from opaque account data.
  303. fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
  304. if Self::valid_account_data(account_data) {
  305. Some(Self::unpack_account_owner_unchecked(account_data))
  306. } else {
  307. None
  308. }
  309. }
  310. /// Unpacks an account's mint from opaque account data.
  311. fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
  312. if Self::valid_account_data(account_data) {
  313. Some(Self::unpack_account_mint_unchecked(account_data))
  314. } else {
  315. None
  316. }
  317. }
  318. }
  319. /// The offset of state field in Account's C representation
  320. pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
  321. /// Check if the account data buffer represents an initialized account.
  322. /// This is checking the `state` (AccountState) field of an Account object.
  323. pub fn is_initialized_account(account_data: &[u8]) -> bool {
  324. *account_data
  325. .get(ACCOUNT_INITIALIZED_INDEX)
  326. .unwrap_or(&(AccountState::Uninitialized as u8))
  327. != AccountState::Uninitialized as u8
  328. }
  329. impl GenericTokenAccount for Account {
  330. fn valid_account_data(account_data: &[u8]) -> bool {
  331. account_data.len() == Account::LEN && is_initialized_account(account_data)
  332. }
  333. }
  334. #[cfg(test)]
  335. mod tests {
  336. use super::*;
  337. #[test]
  338. fn test_mint_unpack_from_slice() {
  339. let src: [u8; 82] = [0; 82];
  340. let mint = Mint::unpack_from_slice(&src).unwrap();
  341. assert!(!mint.is_initialized);
  342. let mut src: [u8; 82] = [0; 82];
  343. src[45] = 2;
  344. let mint = Mint::unpack_from_slice(&src).unwrap_err();
  345. assert_eq!(mint, ProgramError::InvalidAccountData);
  346. }
  347. #[test]
  348. fn test_account_state() {
  349. let account_state = AccountState::default();
  350. assert_eq!(account_state, AccountState::Uninitialized);
  351. }
  352. #[test]
  353. fn test_multisig_unpack_from_slice() {
  354. let src: [u8; 355] = [0; 355];
  355. let multisig = Multisig::unpack_from_slice(&src).unwrap();
  356. assert_eq!(multisig.m, 0);
  357. assert_eq!(multisig.n, 0);
  358. assert!(!multisig.is_initialized);
  359. let mut src: [u8; 355] = [0; 355];
  360. src[0] = 1;
  361. src[1] = 1;
  362. src[2] = 1;
  363. let multisig = Multisig::unpack_from_slice(&src).unwrap();
  364. assert_eq!(multisig.m, 1);
  365. assert_eq!(multisig.n, 1);
  366. assert!(multisig.is_initialized);
  367. let mut src: [u8; 355] = [0; 355];
  368. src[2] = 2;
  369. let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
  370. assert_eq!(multisig, ProgramError::InvalidAccountData);
  371. }
  372. #[test]
  373. fn test_unpack_coption_key() {
  374. let src: [u8; 36] = [0; 36];
  375. let result = unpack_coption_key(&src).unwrap();
  376. assert_eq!(result, COption::None);
  377. let mut src: [u8; 36] = [0; 36];
  378. src[1] = 1;
  379. let result = unpack_coption_key(&src).unwrap_err();
  380. assert_eq!(result, ProgramError::InvalidAccountData);
  381. }
  382. #[test]
  383. fn test_unpack_coption_u64() {
  384. let src: [u8; 12] = [0; 12];
  385. let result = unpack_coption_u64(&src).unwrap();
  386. assert_eq!(result, COption::None);
  387. let mut src: [u8; 12] = [0; 12];
  388. src[0] = 1;
  389. let result = unpack_coption_u64(&src).unwrap();
  390. assert_eq!(result, COption::Some(0));
  391. let mut src: [u8; 12] = [0; 12];
  392. src[1] = 1;
  393. let result = unpack_coption_u64(&src).unwrap_err();
  394. assert_eq!(result, ProgramError::InvalidAccountData);
  395. }
  396. #[test]
  397. fn test_unpack_token_owner() {
  398. // Account data length < Account::LEN, unpack will not return a key
  399. let src: [u8; 12] = [0; 12];
  400. let result = Account::unpack_account_owner(&src);
  401. assert_eq!(result, Option::None);
  402. // The right account data size and intialized, unpack will return some key
  403. let mut src: [u8; Account::LEN] = [0; Account::LEN];
  404. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
  405. let result = Account::unpack_account_owner(&src);
  406. assert!(result.is_some());
  407. // The right account data size and frozen, unpack will return some key
  408. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
  409. let result = Account::unpack_account_owner(&src);
  410. assert!(result.is_some());
  411. // The right account data size and uninitialized, unpack will return None
  412. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
  413. let result = Account::unpack_account_mint(&src);
  414. assert_eq!(result, Option::None);
  415. // Account data length > account data size, unpack will not return a key
  416. let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
  417. let result = Account::unpack_account_owner(&src);
  418. assert_eq!(result, Option::None);
  419. }
  420. #[test]
  421. fn test_unpack_token_mint() {
  422. // Account data length < Account::LEN, unpack will not return a key
  423. let src: [u8; 12] = [0; 12];
  424. let result = Account::unpack_account_mint(&src);
  425. assert_eq!(result, Option::None);
  426. // The right account data size and initialized, unpack will return some key
  427. let mut src: [u8; Account::LEN] = [0; Account::LEN];
  428. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
  429. let result = Account::unpack_account_mint(&src);
  430. assert!(result.is_some());
  431. // The right account data size and frozen, unpack will return some key
  432. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
  433. let result = Account::unpack_account_mint(&src);
  434. assert!(result.is_some());
  435. // The right account data size and uninitialized, unpack will return None
  436. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
  437. let result = Account::unpack_account_mint(&src);
  438. assert_eq!(result, Option::None);
  439. // Account data length > account data size, unpack will not return a key
  440. let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
  441. let result = Account::unpack_account_mint(&src);
  442. assert_eq!(result, Option::None);
  443. }
  444. }