lang: Add custom error for raw constraint (#905)
This commit is contained in:
parent
2a4fa8c94b
commit
d41fb4feb5
|
@ -18,6 +18,7 @@ incremented for features.
|
|||
* ts: `Program<T>` can now be typed with an IDL type ([#795](https://github.com/project-serum/anchor/pull/795)).
|
||||
* 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 custom errors support for raw constraints ([#905](https://github.com/project-serum/anchor/pull/905)).
|
||||
|
||||
### Breaking
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ use syn::parse_macro_input;
|
|||
/// | `#[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(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>)]` | On any type deriving `Accounts` | Executes the given code as a constraint. The expression should evaluate to a boolean. |
|
||||
/// | `#[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. |
|
||||
|
|
|
@ -235,9 +235,10 @@ pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenS
|
|||
|
||||
pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream {
|
||||
let raw = &c.raw;
|
||||
let error = generate_custom_error(&c.error, quote! { ConstraintRaw });
|
||||
quote! {
|
||||
if !(#raw) {
|
||||
return Err(anchor_lang::__private::ErrorCode::ConstraintRaw.into());
|
||||
return Err(#error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -658,3 +659,13 @@ pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2:
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_custom_error(
|
||||
custom_error: &Option<Expr>,
|
||||
error: proc_macro2::TokenStream,
|
||||
) -> proc_macro2::TokenStream {
|
||||
match custom_error {
|
||||
Some(error) => quote! { #error.into() },
|
||||
None => quote! { anchor_lang::__private::ErrorCode::#error.into() },
|
||||
}
|
||||
}
|
||||
|
|
|
@ -618,6 +618,7 @@ pub struct ConstraintLiteral {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct ConstraintRaw {
|
||||
pub raw: Expr,
|
||||
pub error: Option<Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -229,6 +229,7 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
|||
span,
|
||||
ConstraintRaw {
|
||||
raw: stream.parse()?,
|
||||
error: parse_optional_custom_error(&stream)?,
|
||||
},
|
||||
)),
|
||||
"close" => ConstraintToken::Close(Context::new(
|
||||
|
@ -251,6 +252,15 @@ pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
|
|||
Ok(c)
|
||||
}
|
||||
|
||||
fn parse_optional_custom_error(stream: &ParseStream) -> ParseResult<Option<Expr>> {
|
||||
if stream.peek(Token![@]) {
|
||||
stream.parse::<Token![@]>()?;
|
||||
stream.parse().map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ConstraintGroupBuilder<'ty> {
|
||||
pub f_ty: Option<&'ty Ty>,
|
||||
|
|
|
@ -32,6 +32,10 @@ mod errors {
|
|||
pub fn signer_error(_ctx: Context<SignerError>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn raw_custom_error(_ctx: Context<RawCustomError>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -61,10 +65,17 @@ pub struct HasOneAccount {
|
|||
owner: Pubkey,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct RawCustomError<'info> {
|
||||
#[account(constraint = *my_account.key == ID @ MyError::HelloCustom)]
|
||||
my_account: AccountInfo<'info>
|
||||
}
|
||||
|
||||
#[error]
|
||||
pub enum MyError {
|
||||
#[msg("This is an error message clients will automatically display")]
|
||||
Hello,
|
||||
HelloNoMsg = 123,
|
||||
HelloNext,
|
||||
HelloCustom,
|
||||
}
|
||||
|
|
|
@ -112,4 +112,20 @@ describe("errors", () => {
|
|||
assert.equal(err.toString(), errMsg);
|
||||
}
|
||||
});
|
||||
|
||||
it("Emits a raw custom error", async () => {
|
||||
try {
|
||||
const tx = await program.rpc.rawCustomError({
|
||||
accounts: {
|
||||
myAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
});
|
||||
assert.ok(false);
|
||||
} catch (err) {
|
||||
const errMsg = "HelloCustom";
|
||||
assert.equal(err.toString(), errMsg);
|
||||
assert.equal(err.msg, errMsg);
|
||||
assert.equal(err.code, 300 + 125);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue