本文将通过示例解释如何在参数不符合预期时中止交易,并探讨 Solana 与以太坊在错误处理上的差异。
在以太坊开发中,require 语句常用于限制函数参数的有效范围。例如:
function foobar(uint256 x) public {
require(x < 100, "I'm not happy with the number you picked");
// 后续逻辑
}
在上述代码中,如果 x 的值大于等于 100,交易将触发回滚(revert),状态变更将被撤销。
那么在 Solana 上,或者更具体地说,在使用 Anchor 框架时,我们如何实现类似的功能呢?Anchor 提供了一种与 Solidity 的 require 和自定义错误相似的语法。本文将通过示例解释如何在参数不符合预期时中止交易,并探讨 Solana 与以太坊在错误处理上的差异。
以下是一个简单的 Solana 程序,包含一个名为 limit_range 的函数,该函数限制输入值 a 的范围在 10 到 100 之间:
use anchor_lang::prelude::*;
declare_id!("26J67eCSaycMshogamJVnFtYixz613EQRJW5Rsk4nVyp");
#[program]
pub mod learn_anchor_error {
use super::*;
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(())
}
}
#[derive(Accounts)]
pub struct LimitRange {}
#[error_code]
pub enum MyError {
#[msg("a is too big")]
AisTooBig,
#[msg("a is too small")]
AisTooSmall,
}
以下是对应的测试代码:
import * as anchor from "@coral-xyz/anchor";
import { Program, AnchorError } from "@coral-xyz/anchor";
import { LearnAnchorError } from "../target/types/learn_anchor_error";
import { assert } from "chai";
describe("learn_anchor_error", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.LearnAnchorError as Program<LearnAnchorError>;
it("Input test", async () => {
try {
const tx = await program.methods.limitRange(new anchor.BN(9)).rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
const errMsg =
"a is too small";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
try {
const tx = await program.methods.limitRange(new anchor.BN(101)).rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
const errMsg =
"a is too big";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
});
});
learn_anchor_error
Error number: 6001
Error number: 6000
✔ Input test (48ms)
以太坊通过 revert 操作码回滚整个交易,而 Solana 的 Anchor 框架则通过返回错误(err!)来中止执行。这种差异源于两者的设计哲学:以太坊倾向于状态回滚,而 Solana 更倾向于显式返回结果。
Anchor 提供了一个 require! 宏,其功能与 Solidity 的 require 类似,可以让代码更简洁。将之前的 if 判断替换为 require!,代码变为:
pub fn limit_range(ctx: Context<LimitRange>, a: u64) -> Result<()> {
require!(a >= 10, MyError::AisTooSmall);
require!(a <= 100, MyError::AisTooBig);
msg!("Result = {}", a);
Ok(())
}
这种写法不仅更简洁,还提高了可读性。require! 宏本质上是返回错误的语法糖,与手动使用 err! 等价。
在以太坊中,如果交易回滚,即使在 require 之前发出了事件(如 emit),这些日志也不会被记录。例如:
contract DoesNotLog {
event SomeEvent(uint256);
function tryToLog() public {
emit SomeEvent(100);
require(false);
}
}
调用 tryToLog 不会记录任何事件,因为回滚撤销了所有操作。
在 Solana 中,如果在返回错误前使用 msg! 宏记录日志,会发生什么呢?让我们看一个例子:
pub fn func(ctx: Context<LimitRange>) -> Result<()> {
msg!("Will this print?");
return err!(MyError::AlwaysErrors);
}
#[error_code]
pub enum MyError {
#[msg("a is too big")]
AisTooBig,
#[msg("a is too small")]
AisTooSmall,
#[msg("Always errors")]
AlwaysErrors,
}
新增测试脚本:
it("Error test", async () => {
try {
const tx = await program.methods.func().rpc();
console.log("Your transaction signature", tx);
} catch (_err) {
assert.isTrue(_err instanceof AnchorError);
const err: AnchorError = _err;
const errMsg =
"Always errors";
assert.strictEqual(err.error.errorMessage, errMsg);
console.log("Error number:", err.error.errorCode.number);
}
});
结果是:msg!("Will this print?") 不会被记录,因为 Solana 在返回错误时会中止后续处理。然而,如果将 return err! 替换为 Ok(()),日志将被成功记录。这表明 Solana 的日志记录依赖于函数是否成功返回。
在 Anchor 中,所有程序函数的返回类型均为 Result<()>,自定义错误则通过带有 #[error_code] 属性的枚举定义。每个错误会自动分配一个错误码(通常从 6000 开始递增)。
【笔记配套代码】 https://github.com/0xE1337/rareskills_evm_to_solana 【参考资料】 https://learnblockchain.cn/column/119 https://www.rareskills.io/solana-tutorial
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!