accounts.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. use crate::{
  2. AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAssociated,
  3. ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral, ConstraintOwner,
  4. ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState, CpiAccountTy,
  5. CpiStateTy, Field, ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
  6. };
  7. pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
  8. let fields = match &strct.fields {
  9. syn::Fields::Named(fields) => fields.named.iter().map(parse_account_field).collect(),
  10. _ => panic!("invalid input"),
  11. };
  12. AccountsStruct::new(strct.clone(), fields)
  13. }
  14. fn parse_account_field(f: &syn::Field) -> AccountField {
  15. let anchor_attr = parse_account_attr(f);
  16. parse_field(f, anchor_attr)
  17. }
  18. fn parse_account_attr(f: &syn::Field) -> Option<&syn::Attribute> {
  19. let anchor_attrs: Vec<&syn::Attribute> = f
  20. .attrs
  21. .iter()
  22. .filter(|attr| {
  23. if attr.path.segments.len() != 1 {
  24. return false;
  25. }
  26. if attr.path.segments[0].ident != "account" {
  27. return false;
  28. }
  29. true
  30. })
  31. .collect();
  32. match anchor_attrs.len() {
  33. 0 => None,
  34. 1 => Some(anchor_attrs[0]),
  35. _ => panic!("Invalid syntax: please specify one account attribute."),
  36. }
  37. }
  38. fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> AccountField {
  39. let ident = f.ident.clone().unwrap();
  40. let (constraints, is_mut, is_signer, is_init, payer, space, associated_seed) = match anchor {
  41. None => (vec![], false, false, false, None, None, None),
  42. Some(anchor) => parse_constraints(anchor),
  43. };
  44. match is_field_primitive(f) {
  45. true => {
  46. let ty = parse_ty(f);
  47. AccountField::Field(Field {
  48. ident,
  49. ty,
  50. constraints,
  51. is_mut,
  52. is_signer,
  53. is_init,
  54. payer,
  55. space,
  56. associated_seed,
  57. })
  58. }
  59. false => AccountField::AccountsStruct(CompositeField {
  60. ident,
  61. symbol: ident_string(f),
  62. constraints,
  63. raw_field: f.clone(),
  64. }),
  65. }
  66. }
  67. fn is_field_primitive(f: &syn::Field) -> bool {
  68. match ident_string(f).as_str() {
  69. "ProgramState" | "ProgramAccount" | "CpiAccount" | "Sysvar" | "AccountInfo"
  70. | "CpiState" => true,
  71. _ => false,
  72. }
  73. }
  74. fn parse_ty(f: &syn::Field) -> Ty {
  75. let path = match &f.ty {
  76. syn::Type::Path(ty_path) => ty_path.path.clone(),
  77. _ => panic!("invalid account syntax"),
  78. };
  79. match ident_string(f).as_str() {
  80. "ProgramState" => Ty::ProgramState(parse_program_state(&path)),
  81. "CpiState" => Ty::CpiState(parse_cpi_state(&path)),
  82. "ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
  83. "CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
  84. "Sysvar" => Ty::Sysvar(parse_sysvar(&path)),
  85. "AccountInfo" => Ty::AccountInfo,
  86. _ => panic!("invalid account type"),
  87. }
  88. }
  89. fn ident_string(f: &syn::Field) -> String {
  90. let path = match &f.ty {
  91. syn::Type::Path(ty_path) => ty_path.path.clone(),
  92. _ => panic!("invalid account syntax"),
  93. };
  94. // TODO: allow segmented paths.
  95. assert!(path.segments.len() == 1);
  96. let segments = &path.segments[0];
  97. segments.ident.to_string()
  98. }
  99. fn parse_program_state(path: &syn::Path) -> ProgramStateTy {
  100. let account_ident = parse_account(&path);
  101. ProgramStateTy { account_ident }
  102. }
  103. fn parse_cpi_state(path: &syn::Path) -> CpiStateTy {
  104. let account_ident = parse_account(&path);
  105. CpiStateTy { account_ident }
  106. }
  107. fn parse_cpi_account(path: &syn::Path) -> CpiAccountTy {
  108. let account_ident = parse_account(path);
  109. CpiAccountTy { account_ident }
  110. }
  111. fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
  112. let account_ident = parse_account(path);
  113. ProgramAccountTy { account_ident }
  114. }
  115. fn parse_account(path: &syn::Path) -> syn::Ident {
  116. let segments = &path.segments[0];
  117. match &segments.arguments {
  118. syn::PathArguments::AngleBracketed(args) => {
  119. // Expected: <'info, MyType>.
  120. assert!(args.args.len() == 2);
  121. match &args.args[1] {
  122. syn::GenericArgument::Type(syn::Type::Path(ty_path)) => {
  123. // TODO: allow segmented paths.
  124. assert!(ty_path.path.segments.len() == 1);
  125. let path_segment = &ty_path.path.segments[0];
  126. path_segment.ident.clone()
  127. }
  128. _ => panic!("Invalid ProgramAccount"),
  129. }
  130. }
  131. _ => panic!("Invalid ProgramAccount"),
  132. }
  133. }
  134. fn parse_sysvar(path: &syn::Path) -> SysvarTy {
  135. let segments = &path.segments[0];
  136. let account_ident = match &segments.arguments {
  137. syn::PathArguments::AngleBracketed(args) => {
  138. // Expected: <'info, MyType>.
  139. assert!(args.args.len() == 2);
  140. match &args.args[1] {
  141. syn::GenericArgument::Type(syn::Type::Path(ty_path)) => {
  142. // TODO: allow segmented paths.
  143. assert!(ty_path.path.segments.len() == 1);
  144. let path_segment = &ty_path.path.segments[0];
  145. path_segment.ident.clone()
  146. }
  147. _ => panic!("Invalid Sysvar"),
  148. }
  149. }
  150. _ => panic!("Invalid Sysvar"),
  151. };
  152. match account_ident.to_string().as_str() {
  153. "Clock" => SysvarTy::Clock,
  154. "Rent" => SysvarTy::Rent,
  155. "EpochSchedule" => SysvarTy::EpochSchedule,
  156. "Fees" => SysvarTy::Fees,
  157. "RecentBlockhashes" => SysvarTy::RecentBlockHashes,
  158. "SlotHashes" => SysvarTy::SlotHashes,
  159. "SlotHistory" => SysvarTy::SlotHistory,
  160. "StakeHistory" => SysvarTy::StakeHistory,
  161. "Instructions" => SysvarTy::Instructions,
  162. "Rewards" => SysvarTy::Rewards,
  163. _ => panic!("Invalid Sysvar"),
  164. }
  165. }
  166. fn parse_constraints(
  167. anchor: &syn::Attribute,
  168. ) -> (
  169. Vec<Constraint>,
  170. bool,
  171. bool,
  172. bool,
  173. Option<syn::Ident>,
  174. Option<proc_macro2::TokenStream>,
  175. Option<syn::Ident>,
  176. ) {
  177. let mut tts = anchor.tokens.clone().into_iter();
  178. let g_stream = match tts.next().expect("Must have a token group") {
  179. proc_macro2::TokenTree::Group(g) => g.stream(),
  180. _ => panic!("Invalid syntax"),
  181. };
  182. let mut is_init = false;
  183. let mut is_mut = false;
  184. let mut is_signer = false;
  185. let mut constraints = vec![];
  186. let mut is_rent_exempt = None;
  187. let mut payer = None;
  188. let mut space = None;
  189. let mut is_associated = false;
  190. let mut associated_seed = None;
  191. let mut inner_tts = g_stream.into_iter();
  192. while let Some(token) = inner_tts.next() {
  193. match token {
  194. proc_macro2::TokenTree::Ident(ident) => match ident.to_string().as_str() {
  195. "init" => {
  196. is_init = true;
  197. is_mut = true;
  198. // If it's not specified, all program owned accounts default
  199. // to being rent exempt.
  200. if is_rent_exempt.is_none() {
  201. is_rent_exempt = Some(true);
  202. }
  203. }
  204. "mut" => {
  205. is_mut = true;
  206. }
  207. "signer" => {
  208. is_signer = true;
  209. constraints.push(Constraint::Signer(ConstraintSigner {}));
  210. }
  211. "seeds" => {
  212. match inner_tts.next().unwrap() {
  213. proc_macro2::TokenTree::Punct(punct) => {
  214. assert!(punct.as_char() == '=');
  215. punct
  216. }
  217. _ => panic!("invalid syntax"),
  218. };
  219. let seeds = match inner_tts.next().unwrap() {
  220. proc_macro2::TokenTree::Group(g) => g,
  221. _ => panic!("invalid syntax"),
  222. };
  223. constraints.push(Constraint::Seeds(ConstraintSeeds { seeds }))
  224. }
  225. "belongs_to" | "has_one" => {
  226. match inner_tts.next().unwrap() {
  227. proc_macro2::TokenTree::Punct(punct) => {
  228. assert!(punct.as_char() == '=');
  229. punct
  230. }
  231. _ => panic!("invalid syntax"),
  232. };
  233. let join_target = match inner_tts.next().unwrap() {
  234. proc_macro2::TokenTree::Ident(ident) => ident,
  235. _ => panic!("invalid syntax"),
  236. };
  237. constraints.push(Constraint::BelongsTo(ConstraintBelongsTo { join_target }))
  238. }
  239. "owner" => {
  240. match inner_tts.next().unwrap() {
  241. proc_macro2::TokenTree::Punct(punct) => {
  242. assert!(punct.as_char() == '=');
  243. punct
  244. }
  245. _ => panic!("invalid syntax"),
  246. };
  247. let owner_target = match inner_tts.next().unwrap() {
  248. proc_macro2::TokenTree::Ident(ident) => ident,
  249. _ => panic!("invalid syntax"),
  250. };
  251. constraints.push(Constraint::Owner(ConstraintOwner { owner_target }));
  252. }
  253. "rent_exempt" => {
  254. match inner_tts.next() {
  255. None => is_rent_exempt = Some(true),
  256. Some(tkn) => {
  257. match tkn {
  258. proc_macro2::TokenTree::Punct(punct) => {
  259. assert!(punct.as_char() == '=');
  260. punct
  261. }
  262. _ => panic!("invalid syntax"),
  263. };
  264. let should_skip = match inner_tts.next().unwrap() {
  265. proc_macro2::TokenTree::Ident(ident) => ident,
  266. _ => panic!("invalid syntax"),
  267. };
  268. match should_skip.to_string().as_str() {
  269. "skip" => {
  270. is_rent_exempt = Some(false);
  271. },
  272. _ => panic!("invalid syntax: omit the rent_exempt attribute to enforce rent exemption"),
  273. };
  274. }
  275. };
  276. }
  277. "executable" => {
  278. constraints.push(Constraint::Executable(ConstraintExecutable {}));
  279. }
  280. "state" => {
  281. match inner_tts.next().unwrap() {
  282. proc_macro2::TokenTree::Punct(punct) => {
  283. assert!(punct.as_char() == '=');
  284. punct
  285. }
  286. _ => panic!("invalid syntax"),
  287. };
  288. let program_target = match inner_tts.next().unwrap() {
  289. proc_macro2::TokenTree::Ident(ident) => ident,
  290. _ => panic!("invalid syntax"),
  291. };
  292. constraints.push(Constraint::State(ConstraintState { program_target }));
  293. }
  294. "associated" => {
  295. is_associated = true;
  296. is_mut = true;
  297. match inner_tts.next().unwrap() {
  298. proc_macro2::TokenTree::Punct(punct) => {
  299. assert!(punct.as_char() == '=');
  300. punct
  301. }
  302. _ => panic!("invalid syntax"),
  303. };
  304. let associated_target = match inner_tts.next().unwrap() {
  305. proc_macro2::TokenTree::Ident(ident) => ident,
  306. _ => panic!("invalid syntax"),
  307. };
  308. constraints.push(Constraint::Associated(ConstraintAssociated {
  309. associated_target,
  310. }));
  311. }
  312. "with" => {
  313. match inner_tts.next().unwrap() {
  314. proc_macro2::TokenTree::Punct(punct) => {
  315. assert!(punct.as_char() == '=');
  316. punct
  317. }
  318. _ => panic!("invalid syntax"),
  319. };
  320. associated_seed = match inner_tts.next().unwrap() {
  321. proc_macro2::TokenTree::Ident(ident) => Some(ident),
  322. _ => panic!("invalid syntax"),
  323. };
  324. }
  325. "payer" => {
  326. match inner_tts.next().unwrap() {
  327. proc_macro2::TokenTree::Punct(punct) => {
  328. assert!(punct.as_char() == '=');
  329. punct
  330. }
  331. _ => panic!("invalid syntax"),
  332. };
  333. let _payer = match inner_tts.next().unwrap() {
  334. proc_macro2::TokenTree::Ident(ident) => ident,
  335. _ => panic!("invalid syntax"),
  336. };
  337. payer = Some(_payer);
  338. }
  339. "space" => {
  340. match inner_tts.next().unwrap() {
  341. proc_macro2::TokenTree::Punct(punct) => {
  342. assert!(punct.as_char() == '=');
  343. punct
  344. }
  345. _ => panic!("invalid syntax"),
  346. };
  347. match inner_tts.next().unwrap() {
  348. proc_macro2::TokenTree::Literal(literal) => {
  349. let tokens: proc_macro2::TokenStream =
  350. literal.to_string().replace("\"", "").parse().unwrap();
  351. space = Some(tokens);
  352. }
  353. _ => panic!("invalid space"),
  354. }
  355. }
  356. _ => {
  357. panic!("invalid syntax");
  358. }
  359. },
  360. proc_macro2::TokenTree::Punct(punct) => {
  361. if punct.as_char() != ',' {
  362. panic!("invalid syntax");
  363. }
  364. }
  365. proc_macro2::TokenTree::Literal(literal) => {
  366. let tokens: proc_macro2::TokenStream =
  367. literal.to_string().replace("\"", "").parse().unwrap();
  368. constraints.push(Constraint::Literal(ConstraintLiteral { tokens }));
  369. }
  370. _ => {
  371. panic!("invalid syntax");
  372. }
  373. }
  374. }
  375. // If `associated` is given, remove `init` since it's redundant.
  376. if is_associated {
  377. is_init = false;
  378. }
  379. if let Some(is_re) = is_rent_exempt {
  380. match is_re {
  381. false => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
  382. true => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Enforce)),
  383. }
  384. }
  385. (
  386. constraints,
  387. is_mut,
  388. is_signer,
  389. is_init,
  390. payer,
  391. space,
  392. associated_seed,
  393. )
  394. }