state.rs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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, PartialEq, TryFromPrimitive)]
  178. pub enum AccountState {
  179. /// Account is not yet initialized
  180. Uninitialized,
  181. /// Account is initialized; the account owner and/or delegate may perform permitted operations
  182. /// on this account
  183. Initialized,
  184. /// Account has been frozen by the mint freeze authority. Neither the account owner nor
  185. /// the delegate are able to perform operations on this account.
  186. Frozen,
  187. }
  188. impl Default for AccountState {
  189. fn default() -> Self {
  190. AccountState::Uninitialized
  191. }
  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::new(src);
  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 fields
  290. /// 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 account owner
  295. fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
  296. Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
  297. }
  298. /// Call after account length has already been verified to unpack the account mint
  299. fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
  300. Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
  301. }
  302. /// Call after account length has already been verified to unpack a Pubkey at
  303. /// the specified offset. Panics if `account_data.len()` is less than `PUBKEY_BYTES`
  304. fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
  305. bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
  306. }
  307. /// Unpacks an account's owner from opaque account data.
  308. fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
  309. if Self::valid_account_data(account_data) {
  310. Some(Self::unpack_account_owner_unchecked(account_data))
  311. } else {
  312. None
  313. }
  314. }
  315. /// Unpacks an account's mint from opaque account data.
  316. fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
  317. if Self::valid_account_data(account_data) {
  318. Some(Self::unpack_account_mint_unchecked(account_data))
  319. } else {
  320. None
  321. }
  322. }
  323. }
  324. /// The offset of state field in Account's C representation
  325. pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
  326. /// Check if the account data buffer represents an initialized account.
  327. /// This is checking the `state` (AccountState) field of an Account object.
  328. pub fn is_initialized_account(account_data: &[u8]) -> bool {
  329. *account_data
  330. .get(ACCOUNT_INITIALIZED_INDEX)
  331. .unwrap_or(&(AccountState::Uninitialized as u8))
  332. != AccountState::Uninitialized as u8
  333. }
  334. impl GenericTokenAccount for Account {
  335. fn valid_account_data(account_data: &[u8]) -> bool {
  336. account_data.len() == Account::LEN && is_initialized_account(account_data)
  337. }
  338. }
  339. #[cfg(test)]
  340. mod tests {
  341. use super::*;
  342. #[test]
  343. fn test_mint_unpack_from_slice() {
  344. let src: [u8; 82] = [0; 82];
  345. let mint = Mint::unpack_from_slice(&src).unwrap();
  346. assert!(!mint.is_initialized);
  347. let mut src: [u8; 82] = [0; 82];
  348. src[45] = 2;
  349. let mint = Mint::unpack_from_slice(&src).unwrap_err();
  350. assert_eq!(mint, ProgramError::InvalidAccountData);
  351. }
  352. #[test]
  353. fn test_account_state() {
  354. let account_state = AccountState::default();
  355. assert_eq!(account_state, AccountState::Uninitialized);
  356. }
  357. #[test]
  358. fn test_multisig_unpack_from_slice() {
  359. let src: [u8; 355] = [0; 355];
  360. let multisig = Multisig::unpack_from_slice(&src).unwrap();
  361. assert_eq!(multisig.m, 0);
  362. assert_eq!(multisig.n, 0);
  363. assert!(!multisig.is_initialized);
  364. let mut src: [u8; 355] = [0; 355];
  365. src[0] = 1;
  366. src[1] = 1;
  367. src[2] = 1;
  368. let multisig = Multisig::unpack_from_slice(&src).unwrap();
  369. assert_eq!(multisig.m, 1);
  370. assert_eq!(multisig.n, 1);
  371. assert!(multisig.is_initialized);
  372. let mut src: [u8; 355] = [0; 355];
  373. src[2] = 2;
  374. let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
  375. assert_eq!(multisig, ProgramError::InvalidAccountData);
  376. }
  377. #[test]
  378. fn test_unpack_coption_key() {
  379. let src: [u8; 36] = [0; 36];
  380. let result = unpack_coption_key(&src).unwrap();
  381. assert_eq!(result, COption::None);
  382. let mut src: [u8; 36] = [0; 36];
  383. src[1] = 1;
  384. let result = unpack_coption_key(&src).unwrap_err();
  385. assert_eq!(result, ProgramError::InvalidAccountData);
  386. }
  387. #[test]
  388. fn test_unpack_coption_u64() {
  389. let src: [u8; 12] = [0; 12];
  390. let result = unpack_coption_u64(&src).unwrap();
  391. assert_eq!(result, COption::None);
  392. let mut src: [u8; 12] = [0; 12];
  393. src[0] = 1;
  394. let result = unpack_coption_u64(&src).unwrap();
  395. assert_eq!(result, COption::Some(0));
  396. let mut src: [u8; 12] = [0; 12];
  397. src[1] = 1;
  398. let result = unpack_coption_u64(&src).unwrap_err();
  399. assert_eq!(result, ProgramError::InvalidAccountData);
  400. }
  401. #[test]
  402. fn test_unpack_token_owner() {
  403. // Account data length < Account::LEN, unpack will not return a key
  404. let src: [u8; 12] = [0; 12];
  405. let result = Account::unpack_account_owner(&src);
  406. assert_eq!(result, Option::None);
  407. // The right account data size and intialized, unpack will return some key
  408. let mut src: [u8; Account::LEN] = [0; Account::LEN];
  409. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
  410. let result = Account::unpack_account_owner(&src);
  411. assert!(result.is_some());
  412. // The right account data size and frozen, unpack will return some key
  413. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
  414. let result = Account::unpack_account_owner(&src);
  415. assert!(result.is_some());
  416. // The right account data size and uninitialized, unpack will return None
  417. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
  418. let result = Account::unpack_account_mint(&src);
  419. assert_eq!(result, Option::None);
  420. // Account data length > account data size, unpack will not return a key
  421. let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
  422. let result = Account::unpack_account_owner(&src);
  423. assert_eq!(result, Option::None);
  424. }
  425. #[test]
  426. fn test_unpack_token_mint() {
  427. // Account data length < Account::LEN, unpack will not return a key
  428. let src: [u8; 12] = [0; 12];
  429. let result = Account::unpack_account_mint(&src);
  430. assert_eq!(result, Option::None);
  431. // The right account data size and initialized, unpack will return some key
  432. let mut src: [u8; Account::LEN] = [0; Account::LEN];
  433. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
  434. let result = Account::unpack_account_mint(&src);
  435. assert!(result.is_some());
  436. // The right account data size and frozen, unpack will return some key
  437. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
  438. let result = Account::unpack_account_mint(&src);
  439. assert!(result.is_some());
  440. // The right account data size and uninitialized, unpack will return None
  441. src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
  442. let result = Account::unpack_account_mint(&src);
  443. assert_eq!(result, Option::None);
  444. // Account data length > account data size, unpack will not return a key
  445. let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
  446. let result = Account::unpack_account_mint(&src);
  447. assert_eq!(result, Option::None);
  448. }
  449. }