lang: Add custom error for `signer`, `mut`, `has_one`, `owner` and `address` constraints (#913)
This commit is contained in:
parent
1ba6db0afe
commit
58c00770cb
|
@ -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
|
||||
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 },
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
@ -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)]
|
||||
|
|
|
@ -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 {}))
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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")),
|
||||
|
@ -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() {
|
||||
|
@ -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 }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue