Skip to content

Commit

Permalink
lang: Framework defined error codes (#354)
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante authored Jun 9, 2021
1 parent 39d0c62 commit ba99c9c
Show file tree
Hide file tree
Showing 36 changed files with 510 additions and 176 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ incremented for features.

* lang: Allows one to use `remaining_accounts` with `CpiContext` by implementing the `ToAccountMetas` trait on `CpiContext` ([#351](https://github.com/project-serum/anchor/pull/351/files)).

### Breaking

* lang, ts: Framework defined error codes are introduced, reserving error codes 0-300 for Anchor, and 300 and up for user defined error codes ([#354](https://github.com/project-serum/anchor/pull/354)).

## [0.7.0] - 2021-05-31

### Features
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/chat/tests/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe("chat", () => {
assert.ok(msg.from.equals(user));
assert.ok(data.startsWith(messages[idx]));
} else {
assert.ok(new anchor.web3.PublicKey());
assert.ok(anchor.web3.PublicKey.default);
assert.ok(
JSON.stringify(msg.data) === JSON.stringify(new Array(280).fill(0))
);
Expand Down
38 changes: 38 additions & 0 deletions examples/errors/programs/errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use anchor_lang::prelude::*;
#[program]
mod errors {
use super::*;

pub fn hello(_ctx: Context<Hello>) -> Result<()> {
Err(MyError::Hello.into())
}
Expand All @@ -17,11 +18,48 @@ mod errors {
pub fn hello_next(_ctx: Context<Hello>) -> Result<()> {
Err(MyError::HelloNext.into())
}

pub fn mut_error(_ctx: Context<MutError>) -> Result<()> {
Ok(())
}

pub fn belongs_to_error(_ctx: Context<BelongsToError>) -> Result<()> {
Ok(())
}

pub fn signer_error(_ctx: Context<SignerError>) -> Result<()> {
Ok(())
}
}

#[derive(Accounts)]
pub struct Hello {}

#[derive(Accounts)]
pub struct MutError<'info> {
#[account(mut)]
my_account: AccountInfo<'info>,
}

#[derive(Accounts)]
pub struct BelongsToError<'info> {
#[account(init, belongs_to = owner)]
my_account: ProgramAccount<'info, BelongsToAccount>,
owner: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
}

#[derive(Accounts)]
pub struct SignerError<'info> {
#[account(signer)]
my_account: AccountInfo<'info>,
}

#[account]
pub struct BelongsToAccount {
owner: Pubkey,
}

#[error]
pub enum MyError {
#[msg("This is an error message clients will automatically display")]
Expand Down
75 changes: 72 additions & 3 deletions examples/errors/tests/errors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const assert = require("assert");
const anchor = require('@project-serum/anchor');
const { Account, Transaction, TransactionInstruction } = anchor.web3;

describe("errors", () => {
// Configure the client to use the local cluster.
Expand All @@ -16,7 +17,7 @@ describe("errors", () => {
"This is an error message clients will automatically display";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 100);
assert.equal(err.code, 300);
}
});

Expand All @@ -28,7 +29,7 @@ describe("errors", () => {
const errMsg = "HelloNoMsg";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 100 + 123);
assert.equal(err.code, 300 + 123);
}
});

Expand All @@ -40,7 +41,75 @@ describe("errors", () => {
const errMsg = "HelloNext";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 100 + 124);
assert.equal(err.code, 300 + 124);
}
});

it("Emits a mut error", async () => {
try {
const tx = await program.rpc.mutError({
accounts: {
myAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
},
});
assert.ok(false);
} catch (err) {
const errMsg = "A mut constraint was violated";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 140);
}
});

it("Emits a belongs to error", async () => {
try {
const account = new Account();
const tx = await program.rpc.belongsToError({
accounts: {
myAccount: account.publicKey,
owner: anchor.web3.SYSVAR_RENT_PUBKEY,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
instructions: [
await program.account.belongsToAccount.createInstruction(account),
],
signers: [account],
});
assert.ok(false);
} catch (err) {
const errMsg = "A belongs_to constraint was violated";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 141);
}
});

// This test uses a raw transaction and provider instead of a program
// instance since the client won't allow one to send a transaction
// with an invalid signer account.
it("Emits a signer error", async () => {
try {
const account = new Account();
const tx = new Transaction();
tx.add(
new TransactionInstruction({
keys: [
{
pubkey: anchor.web3.SYSVAR_RENT_PUBKEY,
isWritable: false,
isSigner: false,
},
],
programId: program.programId,
data: program.coder.instruction.encode("signer_error", {}),
})
);
await program.provider.send(tx);
assert.ok(false);
} catch (err) {
const errMsg =
"Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x8e";
assert.equal(err.toString(), errMsg);
}
});
});
2 changes: 1 addition & 1 deletion examples/lockup/migrations/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module.exports = async function (provider) {
});

// Delete the default whitelist entries.
const defaultEntry = { programId: new anchor.web3.PublicKey() };
const defaultEntry = { programId: new anchor.web3.PublicKey.default };
await lockup.state.rpc.whitelistDelete(defaultEntry, {
accounts: {
authority: provider.wallet.publicKey,
Expand Down
14 changes: 7 additions & 7 deletions examples/lockup/tests/lockup.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ describe("Lockup and Registry", () => {
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
assert.ok(lockupAccount.whitelist.length === WHITELIST_SIZE);
lockupAccount.whitelist.forEach((e) => {
assert.ok(e.programId.equals(new anchor.web3.PublicKey()));
assert.ok(e.programId.equals(anchor.web3.PublicKey.default));
});
});

it("Deletes the default whitelisted addresses", async () => {
const defaultEntry = { programId: new anchor.web3.PublicKey() };
const defaultEntry = { programId: anchor.web3.PublicKey.default };
await lockup.state.rpc.whitelistDelete(defaultEntry, {
accounts: {
authority: provider.wallet.publicKey,
Expand Down Expand Up @@ -116,7 +116,7 @@ describe("Lockup and Registry", () => {
await lockup.state.rpc.whitelistAdd(e, { accounts });
},
(err) => {
assert.equal(err.code, 108);
assert.equal(err.code, 308);
assert.equal(err.msg, "Whitelist is full");
return true;
}
Expand Down Expand Up @@ -216,7 +216,7 @@ describe("Lockup and Registry", () => {
});
},
(err) => {
assert.equal(err.code, 107);
assert.equal(err.code, 307);
assert.equal(err.msg, "Insufficient withdrawal balance.");
return true;
}
Expand Down Expand Up @@ -389,7 +389,7 @@ describe("Lockup and Registry", () => {

assert.ok(memberAccount.registrar.equals(registrar.publicKey));
assert.ok(memberAccount.beneficiary.equals(provider.wallet.publicKey));
assert.ok(memberAccount.metadata.equals(new anchor.web3.PublicKey()));
assert.ok(memberAccount.metadata.equals(anchor.web3.PublicKey.default));
assert.equal(
JSON.stringify(memberAccount.balances),
JSON.stringify(balances)
Expand Down Expand Up @@ -781,7 +781,7 @@ describe("Lockup and Registry", () => {
(err) => {
// Solana doesn't propagate errors across CPI. So we receive the registry's error code,
// not the lockup's.
const errorCode = "custom program error: 0x78";
const errorCode = "custom program error: 0x140";
assert.ok(err.toString().split(errorCode).length === 2);
return true;
}
Expand Down Expand Up @@ -863,7 +863,7 @@ describe("Lockup and Registry", () => {
await tryEndUnstake();
},
(err) => {
assert.equal(err.code, 109);
assert.equal(err.code, 309);
assert.equal(err.msg, "The unstake timelock has not yet expired.");
return true;
}
Expand Down
14 changes: 7 additions & 7 deletions examples/zero-copy/tests/zero-copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ describe("zero-copy", () => {
assert.ok(state.authority.equals(program.provider.wallet.publicKey));
assert.ok(state.events.length === 250);
state.events.forEach((event, idx) => {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
});
});

it("Updates zero copy state", async () => {
let event = {
from: new PublicKey(),
from: PublicKey.default,
data: new BN(1234),
};
await program.state.rpc.setEvent(5, event, {
Expand All @@ -44,7 +44,7 @@ describe("zero-copy", () => {
assert.ok(event.from.equals(event.from));
assert.ok(event.data.eq(event.data));
} else {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
}
});
Expand Down Expand Up @@ -175,7 +175,7 @@ describe("zero-copy", () => {
const account = await program.account.eventQ.fetch(eventQ.publicKey);
assert.ok(account.events.length === 25000);
account.events.forEach((event) => {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
});
});
Expand All @@ -196,7 +196,7 @@ describe("zero-copy", () => {
assert.ok(event.from.equals(program.provider.wallet.publicKey));
assert.ok(event.data.toNumber() === 48);
} else {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
}
});
Expand All @@ -219,7 +219,7 @@ describe("zero-copy", () => {
assert.ok(event.from.equals(program.provider.wallet.publicKey));
assert.ok(event.data.toNumber() === 1234);
} else {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
}
});
Expand All @@ -245,7 +245,7 @@ describe("zero-copy", () => {
assert.ok(event.from.equals(program.provider.wallet.publicKey));
assert.ok(event.data.toNumber() === 99);
} else {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
}
});
Expand Down
20 changes: 10 additions & 10 deletions lang/attribute/account/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ pub fn account(
impl anchor_lang::AccountDeserialize for #account_name {
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
if buf.len() < #discriminator.len() {
return Err(ProgramError::AccountDataTooSmall);
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
}
let given_disc = &buf[..8];
if &#discriminator != given_disc {
return Err(ProgramError::InvalidInstructionData);
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
}
Self::try_deserialize_unchecked(buf)
}
Expand All @@ -144,32 +144,32 @@ pub fn account(

impl anchor_lang::AccountSerialize for #account_name {
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), ProgramError> {
writer.write_all(&#discriminator).map_err(|_| ProgramError::InvalidAccountData)?;
writer.write_all(&#discriminator).map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?;
AnchorSerialize::serialize(
self,
writer
)
.map_err(|_| ProgramError::InvalidAccountData)?;
.map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?;
Ok(())
}
}

impl anchor_lang::AccountDeserialize for #account_name {
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
if buf.len() < #discriminator.len() {
return Err(ProgramError::AccountDataTooSmall);
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
}
let given_disc = &buf[..8];
if &#discriminator != given_disc {
return Err(ProgramError::InvalidInstructionData);
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
}
Self::try_deserialize_unchecked(buf)
}

fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
let mut data: &[u8] = &buf[8..];
AnchorDeserialize::deserialize(&mut data)
.map_err(|_| ProgramError::InvalidAccountData)
.map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotDeserialize.into())
}
}

Expand Down Expand Up @@ -327,8 +327,8 @@ pub fn zero_copy(
let account_strct = parse_macro_input!(item as syn::ItemStruct);

proc_macro::TokenStream::from(quote! {
#[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)]
#[repr(packed)]
#account_strct
#[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)]
#[repr(packed)]
#account_strct
})
}
Loading

0 comments on commit ba99c9c

Please sign in to comment.