Solana Anchor 框架下的 Require 与自定义错误

  • 0xE
  • 发布于 2025-03-22 09:18
  • 阅读 324

本文将通过示例解释如何在参数不符合预期时中止交易,并探讨 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 与以太坊在错误处理上的差异。

Anchor 中的错误处理示例

以下是一个简单的 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)

Solana 与以太坊的区别

以太坊通过 revert 操作码回滚整个交易,而 Solana 的 Anchor 框架则通过返回错误(err!)来中止执行。这种差异源于两者的设计哲学:以太坊倾向于状态回滚,而 Solana 更倾向于显式返回结果。

使用 require! 宏简化代码

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 的日志记录依赖于函数是否成功返回。

Solana 与 Solidity 错误处理的本质差异

  • Solidity:require 使用 revert 操作码终止执行,回滚所有状态变更。
  • Solana:Anchor 中没有直接的“终止执行”概念,而是通过返回 Result 类型(Ok(()) 表示成功,Err 表示失败)来处理结果。这类似于 Linux 系统调用返回 0 表示成功、非 0 表示失败。

在 Anchor 中,所有程序函数的返回类型均为 Result<()>,自定义错误则通过带有 #[error_code] 属性的枚举定义。每个错误会自动分配一个错误码(通常从 6000 开始递增)。


【笔记配套代码】 https://github.com/0xE1337/rareskills_evm_to_solana 【参考资料】 https://learnblockchain.cn/column/119 https://www.rareskills.io/solana-tutorial

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
0xE
0xE
0x59f6...a17e
17年进入币圈,Web3 开发者。刨根问底探链上真相,品味坎坷悟 Web3 人生。