本文将通过示例解释如何在参数不符合预期时中止交易,并探讨 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
 
                如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!