Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: Add custom error for signer, mut, has_one, owner and address constraints #913

Merged
merged 7 commits into from
Oct 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ incremented for features.
* lang: Add `mint::freeze_authority` keyword for mint initialization within `#[derive(Accounts)]` ([#835](https://github.com/project-serum/anchor/pull/835)).
* lang: Add `AccountLoader` type for `zero_copy` accounts with support for CPI ([#792](https://github.com/project-serum/anchor/pull/792)).
* lang: Add `#[account(init_if_needed)]` keyword for allowing one to invoke the same instruction even if the account was created already ([#906](https://github.com/project-serum/anchor/pull/906)).
* lang: Add custom errors support for raw constraints ([#905](https://github.com/project-serum/anchor/pull/905)).
* lang: Add custom errors support for `signer`, `mut`, `has_one`, `owner`, raw constraints and `address` ([#905](https://github.com/project-serum/anchor/pull/905), [#913](https://github.com/project-serum/anchor/pull/913)).

### Breaking

Expand Down
10 changes: 5 additions & 5 deletions lang/derive/accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,20 @@ use syn::parse_macro_input;
///
/// | Attribute | Location | Description |
/// |:--|:--|:--|
/// | `#[account(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
/// | `#[account(mut)]` | On `AccountInfo`, `ProgramAccount` or `CpiAccount` structs. | Marks the account as mutable and persists the state transition. |
/// | `#[account(signer)]`<br><br>`#[account(signer @ <custom_error>)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. Custom errors are supported via `@`. |
/// | `#[account(mut)]`<br><br>`#[account(mut @ <custom_error>)]` | On `AccountInfo`, `ProgramAccount` or `CpiAccount` structs. | Marks the account as mutable and persists the state transition. Custom errors are supported via `@`. |
/// | `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, creating the account via the system program. |
/// | `#[account(zero)]` | On `ProgramAccount` structs. | Asserts the account discriminator is zero. |
/// | `#[account(close = <target>)]` | On `ProgramAccount` and `Loader` structs. | Marks the account as being closed at the end of the instruction's execution, sending the rent exemption lamports to the specified <target>. |
/// | `#[account(has_one = <target>)]` | On `ProgramAccount` or `CpiAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. |
/// | `#[account(has_one = <target>)]`<br><br>`#[account(has_one = <target> @ <custom_error>)]` | On `ProgramAccount` or `CpiAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. Custom errors are supported via `@`. |
/// | `#[account(seeds = [<seeds>], bump? = <target>, payer? = <target>, space? = <target>, owner? = <target>)]` | On `AccountInfo` structs | Seeds for the program derived address an `AccountInfo` struct represents. If bump is provided, then appends it to the seeds. On initialization, validates the given bump is the bump provided by `Pubkey::find_program_address`.|
/// | `#[account(constraint = <expression>)]`<br><br>`#[account(constraint = <expression> @ <custom_error>)]` | On any type deriving `Accounts` | Executes the given code as a constraint. The expression should evaluate to a boolean. Custom errors are supported via `@`. |
/// | `#[account("<literal>")]` | Deprecated | Executes the given code literal as a constraint. The literal should evaluate to a boolean. |
/// | `#[account(rent_exempt = <skip>)]` | On `AccountInfo` or `ProgramAccount` structs | Optional attribute to skip the rent exemption check. By default, all accounts marked with `#[account(init)]` will be rent exempt, and so this should rarely (if ever) be used. Similarly, omitting `= skip` will mark the account rent exempt. |
/// | `#[account(executable)]` | On `AccountInfo` structs | Checks the given account is an executable program. |
/// | `#[account(state = <target>)]` | On `CpiState` structs | Checks the given state is the canonical state account for the target program. |
/// | `#[account(owner = <target>)]` | On `CpiState`, `CpiAccount`, and `AccountInfo` | Checks the account owner matches the target. |
/// | `#[account(address = <pubkey>)]` | On `AccountInfo` and `Account` | Checks the account key matches the pubkey. |
/// | `#[account(owner = <target>)]`<br><br>`#[account(owner = <target> @ <custom_error>)]` | On `CpiState`, `CpiAccount`, and `AccountInfo` | Checks the account owner matches the target. Custom errors are supported via `@`. |
/// | `#[account(address = <pubkey>)]`<br><br>`#[account(address = <pubkey> @ <custom_error>)]` | On `AccountInfo` and `Account` | Checks the account key matches the pubkey. Custom errors are supported via `@`. |
// TODO: How do we make the markdown render correctly without putting everything
// on absurdly long lines?
#[proc_macro_derive(Accounts, attributes(account, instruction))]
Expand Down
19 changes: 12 additions & 7 deletions lang/syn/src/codegen/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,10 @@ fn generate_constraint_composite(_f: &CompositeField, c: &Constraint) -> proc_ma
fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream {
let field = &f.ident;
let addr = &c.address;
let error = generate_custom_error(&c.error, quote! { ConstraintAddress });
quote! {
if #field.to_account_info().key != &#addr {
return Err(anchor_lang::__private::ErrorCode::ConstraintAddress.into());
return Err(#error);
}
}
}
Expand Down Expand Up @@ -173,11 +174,12 @@ pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2:
}
}

pub fn generate_constraint_mut(f: &Field, _c: &ConstraintMut) -> proc_macro2::TokenStream {
pub fn generate_constraint_mut(f: &Field, c: &ConstraintMut) -> proc_macro2::TokenStream {
let ident = &f.ident;
let error = generate_custom_error(&c.error, quote! { ConstraintMut });
quote! {
if !#ident.to_account_info().is_writable {
return Err(anchor_lang::__private::ErrorCode::ConstraintMut.into());
return Err(#error);
}
}
}
Expand All @@ -190,14 +192,15 @@ pub fn generate_constraint_has_one(f: &Field, c: &ConstraintHasOne) -> proc_macr
Ty::AccountLoader(_) => quote! {#ident.load()?},
_ => quote! {#ident},
};
let error = generate_custom_error(&c.error, quote! { ConstraintHasOne });
quote! {
if &#field.#target != #target.to_account_info().key {
return Err(anchor_lang::__private::ErrorCode::ConstraintHasOne.into());
return Err(#error);
}
}
}

pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macro2::TokenStream {
pub fn generate_constraint_signer(f: &Field, c: &ConstraintSigner) -> proc_macro2::TokenStream {
let ident = &f.ident;
let info = match f.ty {
Ty::AccountInfo => quote! { #ident },
Expand All @@ -208,9 +211,10 @@ pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macr
Ty::CpiAccount(_) => quote! { #ident.to_account_info() },
_ => panic!("Invalid syntax: signer cannot be specified."),
};
let error = generate_custom_error(&c.error, quote! { ConstraintSigner });
quote! {
if !#info.is_signer {
return Err(anchor_lang::__private::ErrorCode::ConstraintSigner.into());
return Err(#error);
}
}
}
Expand Down Expand Up @@ -246,9 +250,10 @@ pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream {
pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
let ident = &f.ident;
let owner_address = &c.owner_address;
let error = generate_custom_error(&c.error, quote! { ConstraintOwner });
quote! {
if #ident.to_account_info().owner != &#owner_address {
return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into());
return Err(#error);
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions lang/syn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,14 +605,19 @@ pub struct ConstraintInitIfNeeded {}
pub struct ConstraintZeroed {}

#[derive(Debug, Clone)]
pub struct ConstraintMut {}
pub struct ConstraintMut {
pub error: Option<Expr>,
}

#[derive(Debug, Clone)]
pub struct ConstraintSigner {}
pub struct ConstraintSigner {
pub error: Option<Expr>,
}

#[derive(Debug, Clone)]
pub struct ConstraintHasOne {
pub join_target: Expr,
pub error: Option<Expr>,
}

#[derive(Debug, Clone)]
Expand All @@ -629,11 +634,13 @@ pub struct ConstraintRaw {
#[derive(Debug, Clone)]
pub struct ConstraintOwner {
pub owner_address: Expr,
pub error: Option<Expr>,
}

#[derive(Debug, Clone)]
pub struct ConstraintAddress {
pub address: Expr,
pub error: Option<Expr>,
}

#[derive(Debug, Clone)]
Expand Down
23 changes: 18 additions & 5 deletions lang/syn/src/parser/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,18 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
ConstraintInit { if_needed: true },
)),
"zero" => ConstraintToken::Zeroed(Context::new(ident.span(), ConstraintZeroed {})),
"mut" => ConstraintToken::Mut(Context::new(ident.span(), ConstraintMut {})),
"signer" => ConstraintToken::Signer(Context::new(ident.span(), ConstraintSigner {})),
"mut" => ConstraintToken::Mut(Context::new(
ident.span(),
ConstraintMut {
error: parse_optional_custom_error(&stream)?,
},
)),
"signer" => ConstraintToken::Signer(Context::new(
ident.span(),
ConstraintSigner {
error: parse_optional_custom_error(&stream)?,
},
)),
"executable" => {
ConstraintToken::Executable(Context::new(ident.span(), ConstraintExecutable {}))
}
Expand Down Expand Up @@ -183,12 +193,14 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
span,
ConstraintHasOne {
join_target: stream.parse()?,
error: parse_optional_custom_error(&stream)?,
},
)),
"owner" => ConstraintToken::Owner(Context::new(
span,
ConstraintOwner {
owner_address: stream.parse()?,
error: parse_optional_custom_error(&stream)?,
},
)),
"rent_exempt" => ConstraintToken::RentExempt(Context::new(
Expand Down Expand Up @@ -249,6 +261,7 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
span,
ConstraintAddress {
address: stream.parse()?,
error: parse_optional_custom_error(&stream)?,
},
)),
_ => return Err(ParseError::new(ident.span(), "Invalid attribute")),
Expand Down Expand Up @@ -340,7 +353,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
None => self
.mutable
.replace(Context::new(i.span(), ConstraintMut {})),
.replace(Context::new(i.span(), ConstraintMut { error: None })),
};
// Rent exempt if not explicitly skipped.
if self.rent_exempt.is_none() {
Expand All @@ -359,7 +372,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
if self.signer.is_none() && self.seeds.is_none() && self.associated_token_mint.is_none()
{
self.signer
.replace(Context::new(i.span(), ConstraintSigner {}));
.replace(Context::new(i.span(), ConstraintSigner { error: None }));
}
}

Expand All @@ -374,7 +387,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
}
None => self
.mutable
.replace(Context::new(z.span(), ConstraintMut {})),
.replace(Context::new(z.span(), ConstraintMut { error: None })),
};
// Rent exempt if not explicitly skipped.
if self.rent_exempt.is_none() {
Expand Down