ERROR HANDLER
Require In solidity
in solidity we use require
to verify user's input, the EVM will revert if x is greater or equals to 100. ( Revert means the state won't be changed.)
function foobar(uint256 x) public {
require(x < 100, "I'm not happy with the number you picked");
// rest of the function logic
}
Code
create a new program, you can also get the source code here: day_4
anchor new day_4
update the code of file:day_4/src/lib.rs
to:
use anchor_lang::prelude::*;
declare_id!("5q5xqHhCFFtLpBSL7byhLihBdcjXZYDqjyfB8QZE4BGR");
#[program]
pub mod day_4 {
use super::*;
// 1. this function will return an error, but the tx won't revert, differ from solidity!
pub fn limit_range(_ctx: Context<LimitRange>, a: u64) -> Result<()> {
if a < 10 {
return err!(MyError::AisTooSmall);
}
if a > 100 {
return err!(MyError::AisTooBig);
}
msg!("Result = {}", a);
Ok(())
}
// 2. the require! macro is a shortcut for the above function, won't revert the tx neither
pub fn limit_range_require(_ctx_then: Context<LimitRange>, a: u64) -> Result<()> {
require!(a >= 10, MyError::AisTooSmall);
require!(a <= 100, MyError::AisTooBig);
msg!("Result = {}", a);
Ok(())
}
// 3. won't revert and the msg! macro will not print, cos it return err!() instead of Ok(())
pub fn funcError(_ctx: Context<LimitRange>) -> Result<()> {
msg!("Will this print when return err!() ?");
return err!(MyError::AlwaysErrors);
// output: this msg will not print
}
// 4. will print the msg! macro, cos it return Ok(())
pub fn funcOK(_ctx: Context<LimitRange>) -> Result<()> {
msg!("Will this print when return OK() ?");
// return err!(MyError::AlwaysErrors);
return Ok(());
// output: this msg will print
}
}
#[derive(Accounts)]
pub struct LimitRange {}
#[error_code]
pub enum MyError {
#[msg("a is too small")]
AisTooSmall,
#[msg("a is too big")]
AisTooBig,
#[msg("Always errors")]
AlwaysErrors,
}
Test
create test file for day_4
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Day4 } from "../target/types/day_4";
import { assert } from "chai";
describe("day_4", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Day4 as Program<Day4>;
it.only("Input test too small", async () => {
try {
const tx = await program.methods.limitRange(new anchor.BN(9)).rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
// console.log(_err);
assert.isTrue(_err instanceof anchor.AnchorError);
const err: anchor.AnchorError = _err;
const errMsg = "a is too small";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("error number:", err.error.errorCode.number);
}
})
it.only("Input test too big", async () => {
try {
const tx = await program.methods.limitRange(new anchor.BN(101)).rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof anchor.AnchorError);
const err: anchor.AnchorError = _err;
const errMsg =
"a is too big";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
})
it.only("Input test require, too small", async () => {
try {
const tx = await program.methods.limitRangeRequire(new anchor.BN(9)).rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
// console.log(_err);
assert.isTrue(_err instanceof anchor.AnchorError);
const err: anchor.AnchorError = _err;
const errMsg = "a is too small";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("error number:", err.error.errorCode.number);
}
})
it.only("Input test require, too big", async () => {
try {
const tx = await program.methods.limitRangeRequire(new anchor.BN(101)).rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof anchor.AnchorError);
const err: anchor.AnchorError = _err;
const errMsg =
"a is too big";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
})
it.only("Error test funcError", async () => {
try {
const tx = await program.methods.funcError().rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof anchor.AnchorError);
const err: anchor.AnchorError = _err;
const errMsg =
"Always errors";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
});
it.only("Error test funcOK", async () => {
try {
const tx = await program.methods.funcOk().rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof anchor.AnchorError);
const err: anchor.AnchorError = _err;
const errMsg =
"Always errors";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
});
});
Test:
anchor deploy -p day_4
anchor test --skip-local-validator --skip-deploy --skip-build
It it fails to test, delete target folder and run:
anchor test --skip-local-validator
Result
Logs and Test results:
Key Takeaways
error!
andrequire!
are the same, and both of them won't revert the tx, this is quite to solidity, be sure of awareness.- we can fetch the revert under try-catch block.
- the
msg!
won't be printed if the function return an error.
Links
- day4 original article: https://www.rareskills.io/post/solana-require-macro
- source code: https://github.com/dukedaily/solana-expert-code/tree/day_4