constraints.rs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. use crate::*;
  2. use proc_macro2_diagnostics::SpanDiagnosticExt;
  3. use quote::quote;
  4. use syn::Expr;
  5. pub fn generate(f: &Field) -> proc_macro2::TokenStream {
  6. let constraints = linearize(&f.constraints);
  7. let rent = constraints
  8. .iter()
  9. .any(|c| matches!(c, Constraint::RentExempt(ConstraintRentExempt::Enforce)))
  10. .then(|| quote! { let __anchor_rent = Rent::get()?; })
  11. .unwrap_or_else(|| quote! {});
  12. let checks: Vec<proc_macro2::TokenStream> = constraints
  13. .iter()
  14. .map(|c| generate_constraint(f, c))
  15. .collect();
  16. quote! {
  17. #rent
  18. #(#checks)*
  19. }
  20. }
  21. pub fn generate_composite(f: &CompositeField) -> proc_macro2::TokenStream {
  22. let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
  23. .iter()
  24. .filter_map(|c| match c {
  25. Constraint::Raw(_) => Some(c),
  26. Constraint::Literal(_) => Some(c),
  27. _ => panic!("Invariant violation: composite constraints can only be raw or literals"),
  28. })
  29. .map(|c| generate_constraint_composite(f, c))
  30. .collect();
  31. quote! {
  32. #(#checks)*
  33. }
  34. }
  35. // Linearizes the constraint group so that constraints with dependencies
  36. // run after those without.
  37. pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
  38. let ConstraintGroup {
  39. init,
  40. zeroed,
  41. mutable,
  42. signer,
  43. has_one,
  44. literal,
  45. raw,
  46. owner,
  47. rent_exempt,
  48. seeds,
  49. executable,
  50. state,
  51. close,
  52. address,
  53. associated_token,
  54. token_account,
  55. mint,
  56. } = c_group.clone();
  57. let mut constraints = Vec::new();
  58. if let Some(c) = zeroed {
  59. constraints.push(Constraint::Zeroed(c));
  60. }
  61. if let Some(c) = init {
  62. constraints.push(Constraint::Init(c));
  63. }
  64. if let Some(c) = seeds {
  65. constraints.push(Constraint::Seeds(c));
  66. }
  67. if let Some(c) = associated_token {
  68. constraints.push(Constraint::AssociatedToken(c));
  69. }
  70. if let Some(c) = mutable {
  71. constraints.push(Constraint::Mut(c));
  72. }
  73. if let Some(c) = signer {
  74. constraints.push(Constraint::Signer(c));
  75. }
  76. constraints.append(&mut has_one.into_iter().map(Constraint::HasOne).collect());
  77. constraints.append(&mut literal.into_iter().map(Constraint::Literal).collect());
  78. constraints.append(&mut raw.into_iter().map(Constraint::Raw).collect());
  79. if let Some(c) = owner {
  80. constraints.push(Constraint::Owner(c));
  81. }
  82. if let Some(c) = rent_exempt {
  83. constraints.push(Constraint::RentExempt(c));
  84. }
  85. if let Some(c) = executable {
  86. constraints.push(Constraint::Executable(c));
  87. }
  88. if let Some(c) = state {
  89. constraints.push(Constraint::State(c));
  90. }
  91. if let Some(c) = close {
  92. constraints.push(Constraint::Close(c));
  93. }
  94. if let Some(c) = address {
  95. constraints.push(Constraint::Address(c));
  96. }
  97. if let Some(c) = token_account {
  98. constraints.push(Constraint::TokenAccount(c));
  99. }
  100. if let Some(c) = mint {
  101. constraints.push(Constraint::Mint(c));
  102. }
  103. constraints
  104. }
  105. fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
  106. match c {
  107. Constraint::Init(c) => generate_constraint_init(f, c),
  108. Constraint::Zeroed(c) => generate_constraint_zeroed(f, c),
  109. Constraint::Mut(c) => generate_constraint_mut(f, c),
  110. Constraint::HasOne(c) => generate_constraint_has_one(f, c),
  111. Constraint::Signer(c) => generate_constraint_signer(f, c),
  112. Constraint::Literal(c) => generate_constraint_literal(&f.ident, c),
  113. Constraint::Raw(c) => generate_constraint_raw(&f.ident, c),
  114. Constraint::Owner(c) => generate_constraint_owner(f, c),
  115. Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c),
  116. Constraint::Seeds(c) => generate_constraint_seeds(f, c),
  117. Constraint::Executable(c) => generate_constraint_executable(f, c),
  118. Constraint::State(c) => generate_constraint_state(f, c),
  119. Constraint::Close(c) => generate_constraint_close(f, c),
  120. Constraint::Address(c) => generate_constraint_address(f, c),
  121. Constraint::AssociatedToken(c) => generate_constraint_associated_token(f, c),
  122. Constraint::TokenAccount(c) => generate_constraint_token_account(f, c),
  123. Constraint::Mint(c) => generate_constraint_mint(f, c),
  124. }
  125. }
  126. fn generate_constraint_composite(f: &CompositeField, c: &Constraint) -> proc_macro2::TokenStream {
  127. match c {
  128. Constraint::Raw(c) => generate_constraint_raw(&f.ident, c),
  129. Constraint::Literal(c) => generate_constraint_literal(&f.ident, c),
  130. _ => panic!("Invariant violation"),
  131. }
  132. }
  133. fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream {
  134. let field = &f.ident;
  135. let addr = &c.address;
  136. let error = generate_custom_error(
  137. field,
  138. &c.error,
  139. quote! { ConstraintAddress },
  140. &Some(&(quote! { actual }, quote! { expected })),
  141. );
  142. quote! {
  143. {
  144. let actual = #field.key();
  145. let expected = #addr;
  146. if actual != expected {
  147. return #error;
  148. }
  149. }
  150. }
  151. }
  152. pub fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
  153. generate_constraint_init_group(f, c)
  154. }
  155. pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macro2::TokenStream {
  156. let field = &f.ident;
  157. let name_str = field.to_string();
  158. let ty_decl = f.ty_decl();
  159. let from_account_info = f.from_account_info(None, false);
  160. quote! {
  161. let #field: #ty_decl = {
  162. let mut __data: &[u8] = &#field.try_borrow_data()?;
  163. let mut __disc_bytes = [0u8; 8];
  164. __disc_bytes.copy_from_slice(&__data[..8]);
  165. let __discriminator = u64::from_le_bytes(__disc_bytes);
  166. if __discriminator != 0 {
  167. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintZero).with_account_name(#name_str));
  168. }
  169. #from_account_info
  170. };
  171. }
  172. }
  173. pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2::TokenStream {
  174. let field = &f.ident;
  175. let name_str = field.to_string();
  176. let target = &c.sol_dest;
  177. quote! {
  178. if #field.key() == #target.key() {
  179. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintClose).with_account_name(#name_str));
  180. }
  181. }
  182. }
  183. pub fn generate_constraint_mut(f: &Field, c: &ConstraintMut) -> proc_macro2::TokenStream {
  184. let ident = &f.ident;
  185. let error = generate_custom_error(ident, &c.error, quote! { ConstraintMut }, &None);
  186. quote! {
  187. if !#ident.to_account_info().is_writable {
  188. return #error;
  189. }
  190. }
  191. }
  192. pub fn generate_constraint_has_one(f: &Field, c: &ConstraintHasOne) -> proc_macro2::TokenStream {
  193. let target = c.join_target.clone();
  194. let ident = &f.ident;
  195. let field = match &f.ty {
  196. Ty::Loader(_) => quote! {#ident.load()?},
  197. Ty::AccountLoader(_) => quote! {#ident.load()?},
  198. _ => quote! {#ident},
  199. };
  200. let error = generate_custom_error(
  201. ident,
  202. &c.error,
  203. quote! { ConstraintHasOne },
  204. &Some(&(quote! { my_key }, quote! { target_key })),
  205. );
  206. quote! {
  207. {
  208. let my_key = #field.#target;
  209. let target_key = #target.key();
  210. if my_key != target_key {
  211. return #error;
  212. }
  213. }
  214. }
  215. }
  216. pub fn generate_constraint_signer(f: &Field, c: &ConstraintSigner) -> proc_macro2::TokenStream {
  217. let ident = &f.ident;
  218. let info = match f.ty {
  219. Ty::AccountInfo => quote! { #ident },
  220. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  221. Ty::Account(_) => quote! { #ident.to_account_info() },
  222. Ty::Loader(_) => quote! { #ident.to_account_info() },
  223. Ty::AccountLoader(_) => quote! { #ident.to_account_info() },
  224. Ty::CpiAccount(_) => quote! { #ident.to_account_info() },
  225. _ => panic!("Invalid syntax: signer cannot be specified."),
  226. };
  227. let error = generate_custom_error(ident, &c.error, quote! { ConstraintSigner }, &None);
  228. quote! {
  229. if !#info.is_signer {
  230. return #error;
  231. }
  232. }
  233. }
  234. pub fn generate_constraint_literal(
  235. ident: &Ident,
  236. c: &ConstraintLiteral,
  237. ) -> proc_macro2::TokenStream {
  238. let name_str = ident.to_string();
  239. let lit: proc_macro2::TokenStream = {
  240. let lit = &c.lit;
  241. let constraint = lit.value().replace('\"', "");
  242. let message = format!(
  243. "Deprecated. Should be used with constraint: #[account(constraint = {})]",
  244. constraint,
  245. );
  246. lit.span().warning(message).emit_as_item_tokens();
  247. constraint.parse().unwrap()
  248. };
  249. quote! {
  250. if !(#lit) {
  251. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::Deprecated).with_account_name(#name_str));
  252. }
  253. }
  254. }
  255. pub fn generate_constraint_raw(ident: &Ident, c: &ConstraintRaw) -> proc_macro2::TokenStream {
  256. let raw = &c.raw;
  257. let error = generate_custom_error(ident, &c.error, quote! { ConstraintRaw }, &None);
  258. quote! {
  259. if !(#raw) {
  260. return #error;
  261. }
  262. }
  263. }
  264. pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
  265. let ident = &f.ident;
  266. let owner_address = &c.owner_address;
  267. let error = generate_custom_error(
  268. ident,
  269. &c.error,
  270. quote! { ConstraintOwner },
  271. &Some(&(quote! { *my_owner }, quote! { owner_address })),
  272. );
  273. quote! {
  274. {
  275. let my_owner = AsRef::<AccountInfo>::as_ref(&#ident).owner;
  276. let owner_address = #owner_address;
  277. if my_owner != &owner_address {
  278. return #error;
  279. }
  280. }
  281. }
  282. }
  283. pub fn generate_constraint_rent_exempt(
  284. f: &Field,
  285. c: &ConstraintRentExempt,
  286. ) -> proc_macro2::TokenStream {
  287. let ident = &f.ident;
  288. let name_str = ident.to_string();
  289. let info = quote! {
  290. #ident.to_account_info()
  291. };
  292. match c {
  293. ConstraintRentExempt::Skip => quote! {},
  294. ConstraintRentExempt::Enforce => quote! {
  295. if !__anchor_rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
  296. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintRentExempt).with_account_name(#name_str));
  297. }
  298. },
  299. }
  300. }
  301. fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
  302. let field = &f.ident;
  303. let name_str = f.ident.to_string();
  304. let ty_decl = f.ty_decl();
  305. let if_needed = if c.if_needed {
  306. quote! {true}
  307. } else {
  308. quote! {false}
  309. };
  310. let space = &c.space;
  311. // Payer for rent exemption.
  312. let payer = {
  313. let p = &c.payer;
  314. quote! {
  315. let payer = #p.to_account_info();
  316. }
  317. };
  318. // Convert from account info to account context wrapper type.
  319. let from_account_info = f.from_account_info(Some(&c.kind), true);
  320. let from_account_info_unchecked = f.from_account_info(Some(&c.kind), false);
  321. // PDA bump seeds.
  322. let (find_pda, seeds_with_bump) = match &c.seeds {
  323. None => (quote! {}, quote! {}),
  324. Some(c) => {
  325. let seeds = &mut c.seeds.clone();
  326. // If the seeds came with a trailing comma, we need to chop it off
  327. // before we interpolate them below.
  328. if let Some(pair) = seeds.pop() {
  329. seeds.push_value(pair.into_value());
  330. }
  331. let maybe_seeds_plus_comma = (!seeds.is_empty()).then(|| {
  332. quote! { #seeds, }
  333. });
  334. (
  335. quote! {
  336. let (__pda_address, __bump) = Pubkey::find_program_address(
  337. &[#maybe_seeds_plus_comma],
  338. program_id,
  339. );
  340. __bumps.insert(#name_str.to_string(), __bump);
  341. },
  342. quote! {
  343. &[
  344. #maybe_seeds_plus_comma
  345. &[__bump][..]
  346. ][..]
  347. },
  348. )
  349. }
  350. };
  351. match &c.kind {
  352. InitKind::Token { owner, mint } => {
  353. let create_account = generate_create_account(
  354. field,
  355. quote! {anchor_spl::token::TokenAccount::LEN},
  356. quote! {&token_program.key()},
  357. seeds_with_bump,
  358. );
  359. quote! {
  360. // Define the bump and pda variable.
  361. #find_pda
  362. let #field: #ty_decl = {
  363. if !#if_needed || AsRef::<AccountInfo>::as_ref(&#field).owner == &anchor_lang::solana_program::system_program::ID {
  364. // Define payer variable.
  365. #payer
  366. // Create the account with the system program.
  367. #create_account
  368. // Initialize the token account.
  369. let cpi_program = token_program.to_account_info();
  370. let accounts = anchor_spl::token::InitializeAccount {
  371. account: #field.to_account_info(),
  372. mint: #mint.to_account_info(),
  373. authority: #owner.to_account_info(),
  374. rent: rent.to_account_info(),
  375. };
  376. let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
  377. anchor_spl::token::initialize_account(cpi_ctx)?;
  378. }
  379. let pa: #ty_decl = #from_account_info_unchecked;
  380. if #if_needed {
  381. if pa.mint != #mint.key() {
  382. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str).with_pubkeys((pa.mint, #mint.key())));
  383. }
  384. if pa.owner != #owner.key() {
  385. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key())));
  386. }
  387. }
  388. pa
  389. };
  390. }
  391. }
  392. InitKind::AssociatedToken { owner, mint } => {
  393. quote! {
  394. // Define the bump and pda variable.
  395. #find_pda
  396. let #field: #ty_decl = {
  397. if !#if_needed || AsRef::<AccountInfo>::as_ref(&#field).owner == &anchor_lang::solana_program::system_program::ID {
  398. #payer
  399. let cpi_program = associated_token_program.to_account_info();
  400. let cpi_accounts = anchor_spl::associated_token::Create {
  401. payer: payer.to_account_info(),
  402. associated_token: #field.to_account_info(),
  403. authority: #owner.to_account_info(),
  404. mint: #mint.to_account_info(),
  405. system_program: system_program.to_account_info(),
  406. token_program: token_program.to_account_info(),
  407. rent: rent.to_account_info(),
  408. };
  409. let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, cpi_accounts);
  410. anchor_spl::associated_token::create(cpi_ctx)?;
  411. }
  412. let pa: #ty_decl = #from_account_info_unchecked;
  413. if #if_needed {
  414. if pa.mint != #mint.key() {
  415. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str).with_pubkeys((pa.mint, #mint.key())));
  416. }
  417. if pa.owner != #owner.key() {
  418. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key())));
  419. }
  420. if pa.key() != anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) {
  421. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount).with_account_name(#name_str));
  422. }
  423. }
  424. pa
  425. };
  426. }
  427. }
  428. InitKind::Mint {
  429. owner,
  430. decimals,
  431. freeze_authority,
  432. } => {
  433. let create_account = generate_create_account(
  434. field,
  435. quote! {anchor_spl::token::Mint::LEN},
  436. quote! {&token_program.key()},
  437. seeds_with_bump,
  438. );
  439. let freeze_authority = match freeze_authority {
  440. Some(fa) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#fa.key()) },
  441. None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
  442. };
  443. quote! {
  444. // Define the bump and pda variable.
  445. #find_pda
  446. let #field: #ty_decl = {
  447. if !#if_needed || AsRef::<AccountInfo>::as_ref(&#field).owner == &anchor_lang::solana_program::system_program::ID {
  448. // Define payer variable.
  449. #payer
  450. // Create the account with the system program.
  451. #create_account
  452. // Initialize the mint account.
  453. let cpi_program = token_program.to_account_info();
  454. let accounts = anchor_spl::token::InitializeMint {
  455. mint: #field.to_account_info(),
  456. rent: rent.to_account_info(),
  457. };
  458. let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
  459. anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.key(), #freeze_authority)?;
  460. }
  461. let pa: #ty_decl = #from_account_info_unchecked;
  462. if #if_needed {
  463. if pa.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) {
  464. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority).with_account_name(#name_str));
  465. }
  466. if pa.freeze_authority
  467. .as_ref()
  468. .map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa != *expected_fa).unwrap_or(true))
  469. .unwrap_or(#freeze_authority.is_some()) {
  470. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority).with_account_name(#name_str));
  471. }
  472. if pa.decimals != #decimals {
  473. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintDecimals).with_account_name(#name_str).with_values((pa.decimals, #decimals)));
  474. }
  475. }
  476. pa
  477. };
  478. }
  479. }
  480. InitKind::Program { owner } => {
  481. // Define the space variable.
  482. let space = quote! {let space = #space;};
  483. // Define the owner of the account being created. If not specified,
  484. // default to the currently executing program.
  485. let owner = match owner {
  486. None => quote! {
  487. program_id
  488. },
  489. Some(o) => quote! {
  490. &#o
  491. },
  492. };
  493. // CPI to the system program to create the account.
  494. let create_account =
  495. generate_create_account(field, quote! {space}, owner.clone(), seeds_with_bump);
  496. // Put it all together.
  497. quote! {
  498. // Define the bump variable.
  499. #find_pda
  500. let #field = {
  501. let actual_field = #field.to_account_info();
  502. let actual_owner = actual_field.owner;
  503. // Define the account space variable.
  504. #space
  505. // Create the account. Always do this in the event
  506. // if needed is not specified or the system program is the owner.
  507. let pa: #ty_decl = if !#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID {
  508. // Define the payer variable.
  509. #payer
  510. // CPI to the system program to create.
  511. #create_account
  512. // Convert from account info to account context wrapper type.
  513. #from_account_info_unchecked
  514. } else {
  515. // Convert from account info to account context wrapper type.
  516. #from_account_info
  517. };
  518. // Assert the account was created correctly.
  519. if #if_needed {
  520. if space != actual_field.data_len() {
  521. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSpace).with_account_name(#name_str).with_values((space, actual_field.data_len())));
  522. }
  523. if actual_owner != #owner {
  524. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintOwner).with_account_name(#name_str).with_pubkeys((*actual_owner, *#owner)));
  525. }
  526. {
  527. let required_lamports = __anchor_rent.minimum_balance(space);
  528. if pa.to_account_info().lamports() < required_lamports {
  529. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintRentExempt).with_account_name(#name_str));
  530. }
  531. }
  532. }
  533. // Done.
  534. pa
  535. };
  536. }
  537. }
  538. }
  539. }
  540. fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
  541. let name = &f.ident;
  542. let name_str = name.to_string();
  543. let s = &mut c.seeds.clone();
  544. let deriving_program_id = c
  545. .program_seed
  546. .clone()
  547. // If they specified a seeds::program to use when deriving the PDA, use it.
  548. .map(|program_id| quote! { #program_id.key() })
  549. // Otherwise fall back to the current program's program_id.
  550. .unwrap_or(quote! { program_id });
  551. // If the seeds came with a trailing comma, we need to chop it off
  552. // before we interpolate them below.
  553. if let Some(pair) = s.pop() {
  554. s.push_value(pair.into_value());
  555. }
  556. // If the bump is provided with init *and target*, then force it to be the
  557. // canonical bump.
  558. //
  559. // Note that for `#[account(init, seeds)]`, find_program_address has already
  560. // been run in the init constraint.
  561. if c.is_init && c.bump.is_some() {
  562. let b = c.bump.as_ref().unwrap();
  563. quote! {
  564. if #name.key() != __pda_address {
  565. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_pubkeys((#name.key(), __pda_address)));
  566. }
  567. if __bump != #b {
  568. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_values((__bump, #b)));
  569. }
  570. }
  571. }
  572. // Init seeds but no bump. We already used the canonical to create bump so
  573. // just check the address.
  574. //
  575. // Note that for `#[account(init, seeds)]`, find_program_address has already
  576. // been run in the init constraint.
  577. else if c.is_init {
  578. quote! {
  579. if #name.key() != __pda_address {
  580. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_pubkeys((#name.key(), __pda_address)));
  581. }
  582. }
  583. }
  584. // No init. So we just check the address.
  585. else {
  586. let maybe_seeds_plus_comma = (!s.is_empty()).then(|| {
  587. quote! { #s, }
  588. });
  589. let define_pda = match c.bump.as_ref() {
  590. // Bump target not given. Find it.
  591. None => quote! {
  592. let (__pda_address, __bump) = Pubkey::find_program_address(
  593. &[#maybe_seeds_plus_comma],
  594. &#deriving_program_id,
  595. );
  596. __bumps.insert(#name_str.to_string(), __bump);
  597. },
  598. // Bump target given. Use it.
  599. Some(b) => quote! {
  600. let __pda_address = Pubkey::create_program_address(
  601. &[#maybe_seeds_plus_comma &[#b][..]],
  602. &#deriving_program_id,
  603. ).map_err(|_| anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str))?;
  604. },
  605. };
  606. quote! {
  607. // Define the PDA.
  608. #define_pda
  609. // Check it.
  610. if #name.key() != __pda_address {
  611. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_pubkeys((#name.key(), __pda_address)));
  612. }
  613. }
  614. }
  615. }
  616. fn generate_constraint_associated_token(
  617. f: &Field,
  618. c: &ConstraintAssociatedToken,
  619. ) -> proc_macro2::TokenStream {
  620. let name = &f.ident;
  621. let name_str = name.to_string();
  622. let wallet_address = &c.wallet;
  623. let spl_token_mint_address = &c.mint;
  624. quote! {
  625. {
  626. let my_owner = #name.owner;
  627. let wallet_address = #wallet_address.key();
  628. if my_owner != wallet_address {
  629. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((my_owner, wallet_address)));
  630. }
  631. let __associated_token_address = anchor_spl::associated_token::get_associated_token_address(&wallet_address, &#spl_token_mint_address.key());
  632. let my_key = #name.key();
  633. if my_key != __associated_token_address {
  634. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociated).with_account_name(#name_str).with_pubkeys((my_key, __associated_token_address)));
  635. }
  636. }
  637. }
  638. }
  639. fn generate_constraint_token_account(
  640. f: &Field,
  641. c: &ConstraintTokenAccountGroup,
  642. ) -> proc_macro2::TokenStream {
  643. let name = &f.ident;
  644. let authority_check = match &c.authority {
  645. Some(authority) => {
  646. quote! { if #name.owner != #authority.key() { return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into()); } }
  647. }
  648. None => quote! {},
  649. };
  650. let mint_check = match &c.mint {
  651. Some(mint) => {
  652. quote! { if #name.mint != #mint.key() { return Err(anchor_lang::error::ErrorCode::ConstraintTokenMint.into()); } }
  653. }
  654. None => quote! {},
  655. };
  656. quote! {
  657. #authority_check
  658. #mint_check
  659. }
  660. }
  661. fn generate_constraint_mint(f: &Field, c: &ConstraintTokenMintGroup) -> proc_macro2::TokenStream {
  662. let name = &f.ident;
  663. let decimal_check = match &c.decimals {
  664. Some(decimals) => quote! {
  665. if #name.decimals != #decimals {
  666. return Err(anchor_lang::error::ErrorCode::ConstraintMintDecimals.into());
  667. }
  668. },
  669. None => quote! {},
  670. };
  671. let mint_authority_check = match &c.mint_authority {
  672. Some(mint_authority) => quote! {
  673. if #name.mint_authority != anchor_lang::solana_program::program_option::COption::Some(anchor_lang::Key::key(&#mint_authority)) {
  674. return Err(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority.into());
  675. }
  676. },
  677. None => quote! {},
  678. };
  679. let freeze_authority_check = match &c.freeze_authority {
  680. Some(freeze_authority) => quote! {
  681. if #name.freeze_authority != anchor_lang::solana_program::program_option::COption::Some(anchor_lang::Key::key(&#freeze_authority)) {
  682. return Err(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority.into());
  683. }
  684. },
  685. None => quote! {},
  686. };
  687. quote! {
  688. #decimal_check
  689. #mint_authority_check
  690. #freeze_authority_check
  691. }
  692. }
  693. // Generated code to create an account with with system program with the
  694. // given `space` amount of data, owned by `owner`.
  695. //
  696. // `seeds_with_nonce` should be given for creating PDAs. Otherwise it's an
  697. // empty stream.
  698. pub fn generate_create_account(
  699. field: &Ident,
  700. space: proc_macro2::TokenStream,
  701. owner: proc_macro2::TokenStream,
  702. seeds_with_nonce: proc_macro2::TokenStream,
  703. ) -> proc_macro2::TokenStream {
  704. quote! {
  705. // If the account being initialized already has lamports, then
  706. // return them all back to the payer so that the account has
  707. // zero lamports when the system program's create instruction
  708. // is eventually called.
  709. let __current_lamports = #field.lamports();
  710. if __current_lamports == 0 {
  711. // Create the token account with right amount of lamports and space, and the correct owner.
  712. let lamports = __anchor_rent.minimum_balance(#space);
  713. let cpi_accounts = anchor_lang::system_program::CreateAccount {
  714. from: payer.to_account_info(),
  715. to: #field.to_account_info()
  716. };
  717. let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_account_info(), cpi_accounts);
  718. anchor_lang::system_program::create_account(cpi_context.with_signer(&[#seeds_with_nonce]), lamports, #space as u64, #owner)?;
  719. } else {
  720. // Fund the account for rent exemption.
  721. let required_lamports = __anchor_rent
  722. .minimum_balance(#space)
  723. .max(1)
  724. .saturating_sub(__current_lamports);
  725. if required_lamports > 0 {
  726. let cpi_accounts = anchor_lang::system_program::Transfer {
  727. from: payer.to_account_info(),
  728. to: #field.to_account_info(),
  729. };
  730. let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_account_info(), cpi_accounts);
  731. anchor_lang::system_program::transfer(cpi_context, required_lamports)?;
  732. }
  733. // Allocate space.
  734. let cpi_accounts = anchor_lang::system_program::Allocate {
  735. account_to_allocate: #field.to_account_info()
  736. };
  737. let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_account_info(), cpi_accounts);
  738. anchor_lang::system_program::allocate(cpi_context.with_signer(&[#seeds_with_nonce]), #space as u64)?;
  739. // Assign to the spl token program.
  740. let cpi_accounts = anchor_lang::system_program::Assign {
  741. account_to_assign: #field.to_account_info()
  742. };
  743. let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_account_info(), cpi_accounts);
  744. anchor_lang::system_program::assign(cpi_context.with_signer(&[#seeds_with_nonce]), #owner)?;
  745. }
  746. }
  747. }
  748. pub fn generate_constraint_executable(
  749. f: &Field,
  750. _c: &ConstraintExecutable,
  751. ) -> proc_macro2::TokenStream {
  752. let name = &f.ident;
  753. let name_str = name.to_string();
  754. quote! {
  755. if !#name.to_account_info().executable {
  756. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintExecutable).with_account_name(#name_str));
  757. }
  758. }
  759. }
  760. pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2::TokenStream {
  761. let program_target = c.program_target.clone();
  762. let ident = &f.ident;
  763. let name_str = ident.to_string();
  764. let account_ty = match &f.ty {
  765. Ty::CpiState(ty) => &ty.account_type_path,
  766. _ => panic!("Invalid state constraint"),
  767. };
  768. quote! {
  769. // Checks the given state account is the canonical state account for
  770. // the target program.
  771. if #ident.key() != anchor_lang::accounts::cpi_state::CpiState::<#account_ty>::address(&#program_target.key()) {
  772. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintState).with_account_name(#name_str));
  773. }
  774. if AsRef::<AccountInfo>::as_ref(&#ident).owner != &#program_target.key() {
  775. return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintState).with_account_name(#name_str));
  776. }
  777. }
  778. }
  779. fn generate_custom_error(
  780. account_name: &Ident,
  781. custom_error: &Option<Expr>,
  782. error: proc_macro2::TokenStream,
  783. compared_values: &Option<&(proc_macro2::TokenStream, proc_macro2::TokenStream)>,
  784. ) -> proc_macro2::TokenStream {
  785. let account_name = account_name.to_string();
  786. let mut error = match custom_error {
  787. Some(error) => {
  788. quote! { anchor_lang::error::Error::from(#error).with_account_name(#account_name) }
  789. }
  790. None => {
  791. quote! { anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::#error).with_account_name(#account_name) }
  792. }
  793. };
  794. let compared_values = match compared_values {
  795. Some((left, right)) => quote! { .with_pubkeys((#left, #right)) },
  796. None => quote! {},
  797. };
  798. error.extend(compared_values);
  799. quote! {
  800. Err(#error)
  801. }
  802. }