constraints.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. use crate::{
  2. CompositeField, Constraint, ConstraintAddress, ConstraintClose, ConstraintExecutable,
  3. ConstraintGroup, ConstraintHasOne, ConstraintInit, ConstraintLiteral, ConstraintMut,
  4. ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeedsGroup, ConstraintSigner,
  5. ConstraintState, Field, PdaKind, Ty,
  6. };
  7. use proc_macro2_diagnostics::SpanDiagnosticExt;
  8. use quote::quote;
  9. use syn::Expr;
  10. pub fn generate(f: &Field) -> proc_macro2::TokenStream {
  11. let constraints = linearize(&f.constraints);
  12. let rent = constraints
  13. .iter()
  14. .any(|c| matches!(c, Constraint::RentExempt(ConstraintRentExempt::Enforce)))
  15. .then(|| quote! { let __anchor_rent = Rent::get()?; })
  16. .unwrap_or_else(|| quote! {});
  17. let checks: Vec<proc_macro2::TokenStream> = constraints
  18. .iter()
  19. .map(|c| generate_constraint(f, c))
  20. .collect();
  21. quote! {
  22. #rent
  23. #(#checks)*
  24. }
  25. }
  26. pub fn generate_composite(f: &CompositeField) -> proc_macro2::TokenStream {
  27. let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
  28. .iter()
  29. .filter_map(|c| match c {
  30. Constraint::Raw(_) => Some(c),
  31. Constraint::Literal(_) => Some(c),
  32. _ => panic!("Invariant violation: composite constraints can only be raw or literals"),
  33. })
  34. .map(|c| generate_constraint_composite(f, c))
  35. .collect();
  36. quote! {
  37. #(#checks)*
  38. }
  39. }
  40. // Linearizes the constraint group so that constraints with dependencies
  41. // run after those without.
  42. pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
  43. let ConstraintGroup {
  44. init,
  45. mutable,
  46. signer,
  47. has_one,
  48. literal,
  49. raw,
  50. owner,
  51. rent_exempt,
  52. seeds,
  53. executable,
  54. state,
  55. close,
  56. address,
  57. } = c_group.clone();
  58. let mut constraints = Vec::new();
  59. if let Some(c) = seeds {
  60. constraints.push(Constraint::Seeds(c));
  61. }
  62. if let Some(c) = init {
  63. constraints.push(Constraint::Init(c));
  64. }
  65. if let Some(c) = mutable {
  66. constraints.push(Constraint::Mut(c));
  67. }
  68. if let Some(c) = signer {
  69. constraints.push(Constraint::Signer(c));
  70. }
  71. constraints.append(&mut has_one.into_iter().map(Constraint::HasOne).collect());
  72. constraints.append(&mut literal.into_iter().map(Constraint::Literal).collect());
  73. constraints.append(&mut raw.into_iter().map(Constraint::Raw).collect());
  74. if let Some(c) = owner {
  75. constraints.push(Constraint::Owner(c));
  76. }
  77. if let Some(c) = rent_exempt {
  78. constraints.push(Constraint::RentExempt(c));
  79. }
  80. if let Some(c) = executable {
  81. constraints.push(Constraint::Executable(c));
  82. }
  83. if let Some(c) = state {
  84. constraints.push(Constraint::State(c));
  85. }
  86. if let Some(c) = close {
  87. constraints.push(Constraint::Close(c));
  88. }
  89. if let Some(c) = address {
  90. constraints.push(Constraint::Address(c));
  91. }
  92. constraints
  93. }
  94. fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
  95. match c {
  96. Constraint::Init(c) => generate_constraint_init(f, c),
  97. Constraint::Mut(c) => generate_constraint_mut(f, c),
  98. Constraint::HasOne(c) => generate_constraint_has_one(f, c),
  99. Constraint::Signer(c) => generate_constraint_signer(f, c),
  100. Constraint::Literal(c) => generate_constraint_literal(c),
  101. Constraint::Raw(c) => generate_constraint_raw(c),
  102. Constraint::Owner(c) => generate_constraint_owner(f, c),
  103. Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c),
  104. Constraint::Seeds(c) => generate_constraint_seeds(f, c),
  105. Constraint::Executable(c) => generate_constraint_executable(f, c),
  106. Constraint::State(c) => generate_constraint_state(f, c),
  107. Constraint::Close(c) => generate_constraint_close(f, c),
  108. Constraint::Address(c) => generate_constraint_address(f, c),
  109. }
  110. }
  111. fn generate_constraint_composite(_f: &CompositeField, c: &Constraint) -> proc_macro2::TokenStream {
  112. match c {
  113. Constraint::Raw(c) => generate_constraint_raw(c),
  114. Constraint::Literal(c) => generate_constraint_literal(c),
  115. _ => panic!("Invariant violation"),
  116. }
  117. }
  118. fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream {
  119. let field = &f.ident;
  120. let addr = &c.address;
  121. quote! {
  122. if #field.to_account_info().key != &#addr {
  123. return Err(anchor_lang::__private::ErrorCode::ConstraintAddress.into());
  124. }
  125. }
  126. }
  127. pub fn generate_constraint_init(_f: &Field, _c: &ConstraintInit) -> proc_macro2::TokenStream {
  128. quote! {}
  129. }
  130. pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2::TokenStream {
  131. let field = &f.ident;
  132. let target = &c.sol_dest;
  133. quote! {
  134. if #field.to_account_info().key == #target.to_account_info().key {
  135. return Err(anchor_lang::__private::ErrorCode::ConstraintClose.into());
  136. }
  137. }
  138. }
  139. pub fn generate_constraint_mut(f: &Field, _c: &ConstraintMut) -> proc_macro2::TokenStream {
  140. let ident = &f.ident;
  141. quote! {
  142. if !#ident.to_account_info().is_writable {
  143. return Err(anchor_lang::__private::ErrorCode::ConstraintMut.into());
  144. }
  145. }
  146. }
  147. pub fn generate_constraint_has_one(f: &Field, c: &ConstraintHasOne) -> proc_macro2::TokenStream {
  148. let target = c.join_target.clone();
  149. let ident = &f.ident;
  150. let field = match &f.ty {
  151. Ty::Loader(_) => quote! {#ident.load()?},
  152. _ => quote! {#ident},
  153. };
  154. quote! {
  155. if &#field.#target != #target.to_account_info().key {
  156. return Err(anchor_lang::__private::ErrorCode::ConstraintHasOne.into());
  157. }
  158. }
  159. }
  160. pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macro2::TokenStream {
  161. let ident = &f.ident;
  162. let info = match f.ty {
  163. Ty::AccountInfo => quote! { #ident },
  164. Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
  165. _ => panic!("Invalid syntax: signer cannot be specified."),
  166. };
  167. quote! {
  168. // Don't enforce on CPI, since usually a program is signing and so
  169. // the `try_accounts` deserializatoin will fail *if* the one
  170. // tries to manually invoke it.
  171. //
  172. // This check will be performed on the other end of the invocation.
  173. if cfg!(not(feature = "cpi")) {
  174. if !#info.to_account_info().is_signer {
  175. return Err(anchor_lang::__private::ErrorCode::ConstraintSigner.into());
  176. }
  177. }
  178. }
  179. }
  180. pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream {
  181. let lit: proc_macro2::TokenStream = {
  182. let lit = &c.lit;
  183. let constraint = lit.value().replace("\"", "");
  184. let message = format!(
  185. "Deprecated. Should be used with constraint: #[account(constraint = {})]",
  186. constraint,
  187. );
  188. lit.span().warning(message).emit_as_item_tokens();
  189. constraint.parse().unwrap()
  190. };
  191. quote! {
  192. if !(#lit) {
  193. return Err(anchor_lang::__private::ErrorCode::Deprecated.into());
  194. }
  195. }
  196. }
  197. pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream {
  198. let raw = &c.raw;
  199. quote! {
  200. if !(#raw) {
  201. return Err(anchor_lang::__private::ErrorCode::ConstraintRaw.into());
  202. }
  203. }
  204. }
  205. pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
  206. let ident = &f.ident;
  207. let owner_target = c.owner_target.clone();
  208. quote! {
  209. if #ident.to_account_info().owner != #owner_target.to_account_info().key {
  210. return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into());
  211. }
  212. }
  213. }
  214. pub fn generate_constraint_rent_exempt(
  215. f: &Field,
  216. c: &ConstraintRentExempt,
  217. ) -> proc_macro2::TokenStream {
  218. let ident = &f.ident;
  219. let info = quote! {
  220. #ident.to_account_info()
  221. };
  222. match c {
  223. ConstraintRentExempt::Skip => quote! {},
  224. ConstraintRentExempt::Enforce => quote! {
  225. if !__anchor_rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
  226. return Err(anchor_lang::__private::ErrorCode::ConstraintRentExempt.into());
  227. }
  228. },
  229. }
  230. }
  231. pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
  232. if c.is_init {
  233. generate_constraint_seeds_init(f, c)
  234. } else {
  235. generate_constraint_seeds_address(f, c)
  236. }
  237. }
  238. fn generate_constraint_seeds_init(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
  239. let payer = {
  240. let p = &c.payer;
  241. quote! {
  242. let payer = #p.to_account_info();
  243. }
  244. };
  245. let seeds_constraint = generate_constraint_seeds_address(f, c);
  246. let seeds_with_nonce = {
  247. let s = &c.seeds;
  248. match c.bump.as_ref() {
  249. // Bump keyword not given. Just use the seeds.
  250. None => quote! {
  251. [#s]
  252. },
  253. // Bump keyword given.
  254. Some(bump) => match bump {
  255. // Bump target not given. Use the canonical bump.
  256. None => {
  257. quote! {
  258. [
  259. #s,
  260. &[
  261. Pubkey::find_program_address(
  262. &[#s],
  263. program_id,
  264. ).1
  265. ]
  266. ]
  267. }
  268. }
  269. // Bump target given. Use it.
  270. Some(b) => quote! {
  271. [#s, &[#b]]
  272. },
  273. },
  274. }
  275. };
  276. generate_pda(
  277. f,
  278. seeds_constraint,
  279. seeds_with_nonce,
  280. payer,
  281. &c.space,
  282. &c.kind,
  283. )
  284. }
  285. fn generate_constraint_seeds_address(
  286. f: &Field,
  287. c: &ConstraintSeedsGroup,
  288. ) -> proc_macro2::TokenStream {
  289. let name = &f.ident;
  290. let s = &c.seeds;
  291. // If the bump is provided on *initialization*, then force it to be the
  292. // canonical nonce.
  293. if c.is_init && c.bump.is_some() && c.bump.as_ref().unwrap().is_some() {
  294. let b = c.bump.as_ref().unwrap().as_ref().unwrap();
  295. quote! {
  296. let (__program_signer, __bump) = anchor_lang::solana_program::pubkey::Pubkey::find_program_address(
  297. &[#s],
  298. program_id,
  299. );
  300. if #name.to_account_info().key != &__program_signer {
  301. return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
  302. }
  303. if __bump != #b {
  304. return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
  305. }
  306. }
  307. } else {
  308. let seeds = match c.bump.as_ref() {
  309. // Bump keyword not given, so just use the seeds.
  310. None => {
  311. quote! {
  312. [#s]
  313. }
  314. }
  315. // Bump keyword given.
  316. Some(bump) => match bump {
  317. // Bump target not given. Find it.
  318. None => {
  319. quote! {
  320. [
  321. #s,
  322. &[
  323. Pubkey::find_program_address(
  324. &[#s],
  325. program_id,
  326. ).1
  327. ]
  328. ]
  329. }
  330. }
  331. // Bump target given. Use it.
  332. Some(b) => {
  333. quote! {
  334. [#s, &[#b]]
  335. }
  336. }
  337. },
  338. };
  339. quote! {
  340. let __program_signer = Pubkey::create_program_address(
  341. &#seeds[..],
  342. program_id,
  343. ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
  344. if #name.to_account_info().key != &__program_signer {
  345. return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
  346. }
  347. }
  348. }
  349. }
  350. fn parse_ty(f: &Field) -> (proc_macro2::TokenStream, proc_macro2::TokenStream, bool) {
  351. match &f.ty {
  352. Ty::ProgramAccount(ty) => {
  353. let ident = &ty.account_type_path;
  354. (
  355. quote! {
  356. #ident
  357. },
  358. quote! {
  359. anchor_lang::ProgramAccount
  360. },
  361. false,
  362. )
  363. }
  364. Ty::Loader(ty) => {
  365. let ident = &ty.account_type_path;
  366. (
  367. quote! {
  368. #ident
  369. },
  370. quote! {
  371. anchor_lang::Loader
  372. },
  373. true,
  374. )
  375. }
  376. Ty::CpiAccount(ty) => {
  377. let ident = &ty.account_type_path;
  378. (
  379. quote! {
  380. #ident
  381. },
  382. quote! {
  383. anchor_lang::CpiAccount
  384. },
  385. false,
  386. )
  387. }
  388. Ty::AccountInfo => (
  389. quote! {
  390. AccountInfo
  391. },
  392. quote! {},
  393. false,
  394. ),
  395. _ => panic!("Invalid type for initializing a program derived address"),
  396. }
  397. }
  398. pub fn generate_pda(
  399. f: &Field,
  400. seeds_constraint: proc_macro2::TokenStream,
  401. seeds_with_nonce: proc_macro2::TokenStream,
  402. payer: proc_macro2::TokenStream,
  403. space: &Option<Expr>,
  404. kind: &PdaKind,
  405. ) -> proc_macro2::TokenStream {
  406. let field = &f.ident;
  407. let (account_ty, account_wrapper_ty, is_zero_copy) = parse_ty(f);
  408. let (combined_account_ty, try_from) = match f.ty {
  409. Ty::AccountInfo => (
  410. quote! {
  411. AccountInfo
  412. },
  413. quote! {
  414. #field.to_account_info()
  415. },
  416. ),
  417. _ => (
  418. quote! {
  419. #account_wrapper_ty<#account_ty>
  420. },
  421. quote! {
  422. #account_wrapper_ty::try_from_init(
  423. &#field.to_account_info(),
  424. )?
  425. },
  426. ),
  427. };
  428. match kind {
  429. PdaKind::Token { owner, mint } => quote! {
  430. let #field: #combined_account_ty = {
  431. #payer
  432. #seeds_constraint
  433. // Fund the account for rent exemption.
  434. let required_lamports = __anchor_rent
  435. .minimum_balance(anchor_spl::token::TokenAccount::LEN)
  436. .max(1)
  437. .saturating_sub(#field.to_account_info().lamports());
  438. // Create the token account with right amount of lamports and space, and the correct owner.
  439. anchor_lang::solana_program::program::invoke_signed(
  440. &anchor_lang::solana_program::system_instruction::create_account(
  441. payer.to_account_info().key,
  442. #field.to_account_info().key,
  443. required_lamports,
  444. anchor_spl::token::TokenAccount::LEN as u64,
  445. token_program.to_account_info().key,
  446. ),
  447. &[
  448. payer.to_account_info(),
  449. #field.to_account_info(),
  450. system_program.to_account_info().clone(),
  451. ],
  452. &[&#seeds_with_nonce[..]],
  453. )?;
  454. // Initialize the token account.
  455. let cpi_program = token_program.to_account_info();
  456. let accounts = anchor_spl::token::InitializeAccount {
  457. account: #field.to_account_info(),
  458. mint: #mint.to_account_info(),
  459. authority: #owner.to_account_info(),
  460. rent: rent.to_account_info(),
  461. };
  462. let cpi_ctx = CpiContext::new(cpi_program, accounts);
  463. anchor_spl::token::initialize_account(cpi_ctx)?;
  464. anchor_lang::CpiAccount::try_from_init(
  465. &#field.to_account_info(),
  466. )?
  467. };
  468. },
  469. PdaKind::Mint { owner, decimals } => quote! {
  470. let #field: #combined_account_ty = {
  471. #payer
  472. #seeds_constraint
  473. // Fund the account for rent exemption.
  474. let required_lamports = rent
  475. .minimum_balance(anchor_spl::token::Mint::LEN)
  476. .max(1)
  477. .saturating_sub(#field.to_account_info().lamports());
  478. // Create the token account with right amount of lamports and space, and the correct owner.
  479. anchor_lang::solana_program::program::invoke_signed(
  480. &anchor_lang::solana_program::system_instruction::create_account(
  481. payer.to_account_info().key,
  482. #field.to_account_info().key,
  483. required_lamports,
  484. anchor_spl::token::Mint::LEN as u64,
  485. token_program.to_account_info().key,
  486. ),
  487. &[
  488. payer.to_account_info(),
  489. #field.to_account_info(),
  490. system_program.to_account_info().clone(),
  491. ],
  492. &[&#seeds_with_nonce[..]],
  493. )?;
  494. // Initialize the mint account.
  495. let cpi_program = token_program.to_account_info();
  496. let accounts = anchor_spl::token::InitializeMint {
  497. mint: #field.to_account_info(),
  498. rent: rent.to_account_info(),
  499. };
  500. let cpi_ctx = CpiContext::new(cpi_program, accounts);
  501. anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.to_account_info().key, None)?;
  502. anchor_lang::CpiAccount::try_from_init(
  503. &#field.to_account_info(),
  504. )?
  505. };
  506. },
  507. PdaKind::Program { owner } => {
  508. let space = match space {
  509. // If no explicit space param was given, serialize the type to bytes
  510. // and take the length (with +8 for the discriminator.)
  511. None => match is_zero_copy {
  512. false => {
  513. quote! {
  514. let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
  515. }
  516. }
  517. true => {
  518. quote! {
  519. let space = 8 + anchor_lang::__private::bytemuck::bytes_of(&#account_ty::default()).len();
  520. }
  521. }
  522. },
  523. // Explicit account size given. Use it.
  524. Some(s) => quote! {
  525. let space = #s;
  526. },
  527. };
  528. // Owner of the account being created. If not specified,
  529. // default to the currently executing program.
  530. let owner = match owner {
  531. None => quote! {
  532. program_id
  533. },
  534. Some(o) => quote! {
  535. &#o
  536. },
  537. };
  538. quote! {
  539. let #field = {
  540. #space
  541. #payer
  542. #seeds_constraint
  543. let lamports = __anchor_rent.minimum_balance(space);
  544. let ix = anchor_lang::solana_program::system_instruction::create_account(
  545. payer.to_account_info().key,
  546. #field.to_account_info().key,
  547. lamports,
  548. space as u64,
  549. #owner,
  550. );
  551. anchor_lang::solana_program::program::invoke_signed(
  552. &ix,
  553. &[
  554. #field.to_account_info(),
  555. payer.to_account_info(),
  556. system_program.to_account_info(),
  557. ],
  558. &[&#seeds_with_nonce[..]]
  559. ).map_err(|e| {
  560. anchor_lang::solana_program::msg!("Unable to create associated account");
  561. e
  562. })?;
  563. let mut pa: #combined_account_ty = #try_from;
  564. pa
  565. };
  566. }
  567. }
  568. }
  569. }
  570. pub fn generate_constraint_executable(
  571. f: &Field,
  572. _c: &ConstraintExecutable,
  573. ) -> proc_macro2::TokenStream {
  574. let name = &f.ident;
  575. quote! {
  576. if !#name.to_account_info().executable {
  577. return Err(anchor_lang::__private::ErrorCode::ConstraintExecutable.into());
  578. }
  579. }
  580. }
  581. pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2::TokenStream {
  582. let program_target = c.program_target.clone();
  583. let ident = &f.ident;
  584. let account_ty = match &f.ty {
  585. Ty::CpiState(ty) => &ty.account_type_path,
  586. _ => panic!("Invalid state constraint"),
  587. };
  588. quote! {
  589. // Checks the given state account is the canonical state account for
  590. // the target program.
  591. if #ident.to_account_info().key != &anchor_lang::CpiState::<#account_ty>::address(#program_target.to_account_info().key) {
  592. return Err(anchor_lang::__private::ErrorCode::ConstraintState.into());
  593. }
  594. if #ident.to_account_info().owner != #program_target.to_account_info().key {
  595. return Err(anchor_lang::__private::ErrorCode::ConstraintState.into());
  596. }
  597. }
  598. }