diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c7b4638d..5576afc777 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ incremented for features. ## [Unreleased] +#### Fixes + +*lang: Improved error msgs when required programs are missing when using the `init` constraint([#1257](https://github.com/project-serum/anchor/pull/1257)) + ## [0.20.0] - 2022-01-06 ### Fixes diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index 62ac9fa692..038d22d3e4 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -154,6 +154,15 @@ pub enum AccountField { CompositeField(CompositeField), } +impl AccountField { + fn ident(&self) -> &Ident { + match self { + AccountField::Field(field) => &field.ident, + AccountField::CompositeField(c_field) => &c_field.ident, + } + } +} + #[derive(Debug)] pub struct Field { pub ident: Ident, diff --git a/lang/syn/src/parser/accounts/mod.rs b/lang/syn/src/parser/accounts/mod.rs index f2c3b5dc0c..2f92212d35 100644 --- a/lang/syn/src/parser/accounts/mod.rs +++ b/lang/syn/src/parser/accounts/mod.rs @@ -31,9 +31,71 @@ pub fn parse(strct: &syn::ItemStruct) -> ParseResult { )) } }; + + let _ = constraints_cross_checks(&fields)?; + Ok(AccountsStruct::new(strct.clone(), fields, instruction_api)) } +fn constraints_cross_checks(fields: &[AccountField]) -> ParseResult<()> { + // INIT + let init_field = fields.iter().find(|f| { + if let AccountField::Field(field) = f { + field.constraints.init.is_some() + } else { + false + } + }); + if let Some(init_field) = init_field { + // init needs system program. + if fields.iter().all(|f| f.ident() != "system_program") { + return Err(ParseError::new( + init_field.ident().span(), + "the init constraint requires \ + the system_program field to exist in the account \ + validation struct. Use the program type to add \ + the system_program field to your validation struct.", + )); + } + if let AccountField::Field(field) = init_field { + let kind = &field.constraints.init.as_ref().unwrap().kind; + // init token/a_token/mint needs token program. + match kind { + InitKind::Program { .. } => (), + InitKind::Token { .. } + | InitKind::AssociatedToken { .. } + | InitKind::Mint { .. } => { + if fields.iter().all(|f| f.ident() != "token_program") { + return Err(ParseError::new( + init_field.ident().span(), + "the init constraint requires \ + the token_program field to exist in the account \ + validation struct. Use the program type to add \ + the token_program field to your validation struct.", + )); + } + } + } + // a_token needs associated token program. + if let InitKind::AssociatedToken { .. } = kind { + if fields + .iter() + .all(|f| f.ident() != "associated_token_program") + { + return Err(ParseError::new( + init_field.ident().span(), + "the init constraint requires \ + the associated_token_program field to exist in the account \ + validation struct. Use the program type to add \ + the associated_token_program field to your validation struct.", + )); + } + } + } + } + Ok(()) +} + pub fn parse_account_field(f: &syn::Field, has_instruction_api: bool) -> ParseResult { let ident = f.ident.clone().unwrap(); let account_field = match is_field_primitive(f)? {