Solidity错误处理与异常机制完全指南:require、revert、assert源码解析

Solidity错误处理与异常机制封面,require、revert、assert安全防护机制

引言

错误处理是智能合约安全的基石。与传统Web应用不同,区块链上的合约一旦部署就无法修改,任何未被正确处理的错误都可能导致资金损失。Solidity提供了三种主要的错误处理机制:requirerevertassert,但很多开发者对它们之间的差异理解并不深入。

本文将从EVM底层机制出发,剖析这三种错误处理方式的实现原理,分析它们的Gas消耗特性,并给出最佳实践建议。

Solidity三种错误处理方式对比图,require、revert、assert的底层机制与Gas特性解析

一、EVM层面的异常机制

1.1 异常的本质

在EVM层面,异常是一个统一的概念。当交易执行过程中发生异常时,整个状态回滚(除了Gas消耗)。EVM通过几种方式触发异常:

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract EVMExceptionDemo {
    // EVM会触发异常的操作码:
    // 1. 0 (STOP) - 正常停止,但不算异常
    // 2. REVERT (0xfd) - 回滚状态,返还剩余Gas
    // 3. INVALID (0xfe) - 无效操作
    // 4. STATICCALL到 revert/throw - 静态调用违反
    // 5. 算术溢出(EIP-150之前)
    // 6. gasleft() < required - Gas不足
    
    // 以下所有操作都可能触发异常:
    
    // 1. 数组越界访问
    function arrayAccess(uint256 index) public pure returns (uint256) {
        uint256[] memory arr = new uint256[](5);
        return arr[index]; // 如果index >= 5,触发越界
    }
    
    // 2. 除以零
    function divide(uint256 a, uint256 b) public pure returns (uint256) {
        return a / b; // 如果b == 0,触发异常
    }
    
    // 3. 类型转换错误
    function badCast() public pure returns (uint8) {
        uint256 largeNumber = 256;
        return uint8(largeNumber); // 256超出uint8范围
    }
    
    // 4. 空函数调用
    function callEmpty() public pure {
        address(0).call{value: 0}("");
    }
}

1.2 异常传播机制

理解异常的传播机制对于编写安全的合约至关重要:

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract ExceptionPropagation {
    
    // 外部调用失败会导致整个交易回滚
    function externalCallMayFail(address target) public pure {
        // 如果target.call失败,这里的代码不会执行
        // 交易会完全回滚
        (bool success, ) = target.call("");
        require(success);
    }
    
    // 内部函数抛出的异常会传播
    function internalFunction() internal pure {
        revert("Internal error");
    }
    
    function caller() public pure {
        internalFunction();
        // 永远不会执行到这里
        // 异常会传播到调用者
    }
    
    // try-catch可以捕获外部调用的异常
    function tryCatchExample(address target) public returns (bool) {
        try this.externalCall(target) returns (bool success) {
            return success;
        } catch Error(string memory reason) {
            // 捕获revert("reason")
            return false;
        } catch Panic(uint256 panicCode) {
            // 捕获assert失败等panic
            return false;
        } catch bytes memory lowLevelData) {
            // 捕获其他所有异常
            return false;
        }
    }
    
    function externalCall(address target) external pure returns (bool) {
        (bool success, ) = target.call("");
        return success;
    }
}

二、深入理解三种错误处理语句

2.1 require 的实现原理

require是开发者最常用的错误处理语句。它本质上是对revert的包装,提供了更友好的语法:

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * 从编译器视角看:
 * require(condition) 
 *   => if (!condition) { revert Error("condition not met"); }
 *   
 * require(condition, "message")
 *   => if (!condition) { revert Error("message"); }
 *
 * Error(string) 是编译器内置的错误选择器
 */

// require的典型使用场景
contract RequireUsage {
    mapping(address => uint256) public balances;
    address public owner;
    
    constructor() {
        owner = msg.sender;
    }
    
    // 场景1:输入验证
    function deposit(uint256 amount) external {
        require(amount > 0, "Amount must be positive");
        require(amount <= 1e18, "Amount too large"); // 防止意外
        balances[msg.sender] += amount;
    }
    
    // 场景2:状态验证
    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
    }
    
    // 场景3:权限验证
    function sensitiveOperation() external {
        require(msg.sender == owner, "Not authorized");
        // 执行敏感操作
    }
    
    // 场景4:前置条件检查(Invariants)
    function updateBalance(address user, uint256 newBalance) external {
        require(newBalance <= 1e24, "Balance exceeds maximum"); // 不变量检查
        balances[user] = newBalance;
    }
}

2.2 revert 的实现原理

revert直接触发EVM的REVERT操作码,是最低层的错误处理方式:

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * revert 有两种形式:
 * 
 * 1. revert(); - 无参数版本
 * 2. revert("reason"); - 带字符串原因
 * 
 * 在字节码层面:
 * - revert 0 0    -> PUSH1 0, PUSH1 0, REVERT
 * - revert 0 32 "reason" -> 编码reason并REVERT
 * 
 * 错误数据格式:
 * - Function selector: Error(string) = 0x08c379a0
 * - Packed: selector + offset + length + string
 */

contract RevertUsage {
    
    // 自定义错误 - 更Gas高效(Solidity 0.8.4+)
    error InsufficientBalance(uint256 available, uint256 required);
    error TransferFailed();
    error Unauthorized(address caller);
    
    mapping(address => uint256) public balances;
    
    // 传统revert with string
    function oldStyleRevert(uint256 amount) public view {
        if (balances[msg.sender] < amount) {
            revert("Insufficient balance for withdrawal");
        }
    }
    
    // 现代风格 - 自定义错误
    function modernRevert(uint256 amount) public view {
        if (balances[msg.sender] < amount) {
            revert InsufficientBalance({
                available: balances[msg.sender],
                required: amount
            });
        }
    }
    
    // 复杂条件下的revert
    function complexValidation(
        address user,
        uint256 amount,
        uint256 deadline
    ) external view {
        // 多个条件用if-revert组合
        if (user == address(0)) {
            revert("Zero address");
        }
        
        if (amount == 0) {
            revert("Zero amount");
        }
        
        if (block.timestamp > deadline) {
            revert("Transaction expired");
        }
        
        if (balances[user] < amount) {
            revert InsufficientBalance({
                available: balances[user],
                required: amount
            });
        }
    }
    
    // 验证后执行的模式
    function validatedWithdraw(uint256 amount) public {
        // 前置验证
        if (amount == 0) revert("Amount is zero");
        if (balances[msg.sender] < amount) {
            revert InsufficientBalance({
                available: balances[msg.sender],
                required: amount
            });
        }
        
        // 验证通过后执行
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

2.3 assert 的实现原理

assert与前两者有本质不同:它用于检查不应该发生的条件,使用INVALID操作码而非REVERT

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * assert 的关键特性:
 * 
 * 1. 使用 INVALID (0xfe) 操作码,不是 REVERT
 * 2. 消耗所有可用Gas(不回退剩余Gas)
 * 3. 用于检查"不可能发生"的情况(Invariant violation)
 * 
 * 注意:在 Solidity 0.8.0+,算术操作默认会检查溢出
 * assert 主要用于验证内部一致性
 */

// assert的典型使用
contract AssertUsage {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    
    // 检查不变量
    function deposit(uint256 amount) external {
        require(amount > 0, "Amount must be positive");
        
        uint256 oldTotal = totalSupply;
        uint256 oldBalance = balances[msg.sender];
        
        balances[msg.sender] += amount;
        totalSupply += amount;
        
        // 断言不变量
        assert(balances[msg.sender] >= oldBalance);
        assert(totalSupply == oldTotal + amount);
    }
    
    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        
        uint256 oldTotal = totalSupply;
        
        balances[msg.sender] -= amount;
        totalSupply -= amount;
        
        // 验证内部一致性
        assert(totalSupply < oldTotal);
        assert(balances[msg.sender] + amount == oldBalance);
    }
    
    // 检查合约级别的Invariants
    uint256 private constant MAX_SUPPLY = 1000000 ether;
    
    function assertTotalSupplyInvariant() public view {
        assert(totalSupply <= MAX_SUPPLY);
    }
    
    // 检查地址不变量
    address public owner;
    
    constructor() {
        owner = msg.sender;
        // 构造函数中assert确保owner已设置
        assert(owner != address(0));
    }
}

2.4 三者对比

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// 完整对比演示
contract ErrorHandlingComparison {
    
    // ========== require ==========
    // - 用于验证输入和前置条件
    // - 可选错误消息
    // - 失败时退还剩余Gas
    // - 最常用的验证方式
    
    function useRequire(address input) public pure {
        require(input != address(0), "Zero address");
        require(input.code.length > 0, "Not a contract");
    }
    
    // ========== revert ==========
    // - 与require功能相同,但语法更灵活
    // - 适合复杂条件判断
    // - 支持自定义错误类型
    // - 可用于代码块末尾
    
    function useRevert(uint256 amount, uint256 limit) public pure {
        if (amount > limit) {
            revert("Amount exceeds limit");
        }
        // 可以继续其他验证
        if (amount == 0) {
            revert("Zero amount not allowed");
        }
    }
    
    // ========== assert ==========
    // - 仅用于检查不变量
    // - 不应触发的条件
    // - 失败时不退还Gas
    // - 用于内部一致性检查
    
    uint256 public value;
    
    function useAssert() public {
        uint256 oldValue = value;
        value = oldValue + 100;
        // 验证更新逻辑
        assert(value > oldValue);
        assert(value == oldValue + 100);
    }
    
    // ========== 何时使用 ==========
    
    // ✅ 用require:输入验证、权限检查、外部调用结果
    function correctRequireUsage(
        uint256 amount,
        address recipient
    ) public pure {
        require(amount > 0, "Invalid amount");
        require(recipient != address(0), "Invalid recipient");
        require(amount <= 1e18, "Amount too large");
    }
    
    // ✅ 用revert:复杂条件、可自定义错误
    error CustomError(uint256 value);
    
    function correctRevertUsage(uint256 amount) public pure {
        if (amount > 1000) {
            revert CustomError(amount);
        }
    }
    
    // ✅ 用assert:内部不变量检查
    uint256 public counter;
    
    function correctAssertUsage() public {
        uint256 before = counter;
        counter++;
        assert(counter == before + 1); // 永远应该为真
    }
    
    // ❌ 错误示例:滥用assert
    function wrongAssertUsage(uint256 amount) public {
        // 错误:用assert验证用户输入
        assert(amount > 0); // 用户可能传入0,这应该用require
    }
}

三、Gas消耗分析

3.1 不同错误处理方式的Gas差异

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * Gas消耗分析:
 * 
 * 1. require(condition) / require(condition, "msg")
 *    - condition执行:可变
 *    - 失败时:REVERT + 数据Gas
 *    - 字符串消息:每个字节4Gas(完全通过时32Gas)
 * 
 * 2. revert() / revert("msg")
 *    - 无condition评估(如果直接revert)
 *    - REVERT操作:0Gas
 *    - 数据Gas:与require相同
 * 
 * 3. assert失败
 *    - INVALID操作:0Gas
 *    - 但消耗所有剩余Gas
 *    - 不推荐在生产环境频繁触发
 * 
 * 4. 自定义错误
 *    - 比字符串消息节省大量Gas
 *    - Example: revert("Insufficient balance") 
 *      vs revert InsufficientBalance()
 */

contract GasComparison {
    error ZeroAmount();
    error NegativeAmount(int256 amount);
    
    // Gas分析:这种方式Gas最低
    function checkCustomError(uint256 amount) public pure {
        if (amount == 0) {
            revert ZeroAmount();
        }
        if (int256(amount) < 0) {
            revert NegativeAmount(int256(amount));
        }
    }
    
    // Gas分析:中等
    function checkRequire(uint256 amount) public pure {
        require(amount > 0);
    }
    
    // Gas分析:最高
    function checkRevertString(uint256 amount) public pure {
        if (amount == 0) {
            revert("Amount must be greater than zero");
        }
    }
    
    // 优化后的验证函数
    uint256 public constant MAX_AMOUNT = 1e18;
    
    function optimizedValidate(uint256 amount) public pure {
        // 单个require检查多个条件
        require(
            amount > 0 && amount <= MAX_AMOUNT,
            amount == 0 ? "Zero amount" : "Amount exceeds maximum"
        );
    }
}

3.2 实际Gas测量

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";

contract GasMeasurement is Test {
    
    ErrorHandlingBenchmark benchmark;
    
    function setUp() public {
        benchmark = new ErrorHandlingBenchmark();
    }
    
    function testGasRequireWithoutMessage() public {
        uint256 gasBefore = gasleft();
        benchmark.requireWithoutMessage(100);
        uint256 gasUsed = gasBefore - gasleft();
        console.log("require without message:", gasUsed);
    }
    
    function testGasRequireWithMessage() public {
        uint256 gasBefore = gasleft();
        benchmark.requireWithMessage(100);
        uint256 gasUsed = gasBefore - gasleft();
        console.log("require with message:", gasUsed);
    }
    
    function testGasCustomError() public {
        uint256 gasBefore = gasleft();
        benchmark.customError(100);
        uint256 gasUsed = gasBefore - gasleft();
        console.log("custom error:", gasUsed);
    }
}

contract ErrorHandlingBenchmark {
    error ZeroAmount();
    
    function requireWithoutMessage(uint256 amount) public pure {
        require(amount > 0);
    }
    
    function requireWithMessage(uint256 amount) public pure {
        require(amount > 0, "Amount must be greater than zero");
    }
    
    function customError(uint256 amount) public pure {
        if (amount == 0) {
            revert ZeroAmount();
        }
    }
}

四、最佳实践与高级模式

4.1 自定义错误的设计

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * 自定义错误设计原则:
 * 
 * 1. 错误名称要清晰表达问题
 * 2. 包含足够的上下文信息
 * 3. 避免重复的错误类型
 */

contract CustomErrors {
    // ========== 输入验证错误 ==========
    error InvalidAmount(uint256 amount);
    error ZeroAddress();
    error InvalidAddress(address addr);
    
    // ========== 状态错误 ==========
    error InsufficientBalance(uint256 available, uint256 required);
    error ExceedsAllowance(uint256 allowance, uint256 requested);
    error OperationPaused();
    
    // ========== 权限错误 ==========
    error Unauthorized();
    error UnauthorizedCaller(address caller);
    error RequiresRole(bytes32 role, address caller);
    
    // ========== 时间相关错误 ==========
    error DeadlinePassed(uint256 deadline);
    error TooEarly(uint256 currentTime, uint256 earliestTime);
    
    mapping(address => uint256) public balances;
    address public owner;
    bool public paused;
    
    modifier whenNotPaused() {
        if (paused) revert OperationPaused();
        _;
    }
    
    modifier onlyOwner() {
        if (msg.sender != owner) revert Unauthorized();
        _;
    }
    
    function deposit(uint256 amount) external whenNotPaused {
        if (amount == 0) revert InvalidAmount(amount);
        balances[msg.sender] += amount;
    }
    
    function withdraw(uint256 amount) external whenNotPaused {
        uint256 balance = balances[msg.sender];
        if (balance < amount) {
            revert InsufficientBalance(balance, amount);
        }
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

4.2 错误处理与用户体验

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * 前端可以根据错误类型提供更好的用户体验
 */

// 合约定义标准化的错误接口
interface IStandardErrors {
    error InsufficientBalance();
    error Unauthorized();
    error InvalidInput();
}

// 合约实现
contract UserFriendlyContract is IStandardErrors {
    mapping(address => uint256) public balances;
    
    function deposit() external payable {
        require(msg.value > 0, "Must send ETH");
        balances[msg.sender] += msg.value;
    }
    
    function withdraw(uint256 amount) external {
        if (amount == 0) revert InvalidInput();
        if (balances[msg.sender] < amount) revert InsufficientBalance();
        
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

// 前端可以通过解析错误数据来判断错误类型
/*
JavaScript 示例:

try {
    await contract.withdraw(amount);
} catch (error) {
    if (error.data) {
        const errorSelector = error.data.slice(0, 10);
        
        switch (errorSelector) {
            case '0x...': // InsufficientBalance selector
                showError('Your balance is insufficient');
                break;
            case '0x...': // Unauthorized selector
                showError('You are not authorized');
                break;
            default:
                showError('Transaction failed');
        }
    }
}
*/

4.3 防御性编程模式

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract DefensiveProgramming {
    
    // 1. 检查返回值
    mapping(address => uint256) public balances;
    
    function safeTransfer(address to, uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        
        balances[msg.sender] -= amount;
        
        // 检查返回值
        (bool success, ) = to.call{value: amount}("");
        if (!success) {
            // 恢复状态
            balances[msg.sender] += amount;
            revert("Transfer failed");
        }
    }
    
    // 2. 使用SafeMath(0.8.0前)或内置溢出检查(0.8.0+)
    function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {
        return a + b; // Solidity 0.8.0+ 自动检查
    }
    
    // 3. 验证前置条件
    function complexOperation(
        address token,
        uint256 amount,
        uint256 deadline
    ) public {
        // 时间验证
        require(block.timestamp <= deadline, "Deadline passed");
        
        // 地址验证
        require(token != address(0), "Zero token address");
        require(token.code.length > 0, "Not a contract");
        
        // 数值验证
        require(amount > 0, "Zero amount");
        require(amount <= 1e18, "Amount too large");
        
        // 执行操作
    }
    
    // 4. 不变量检查
    uint256 public totalDeposits;
    mapping(address => uint256) public deposits;
    
    function deposit(uint256 amount) external {
        require(amount > 0);
        
        deposits[msg.sender] += amount;
        totalDeposits += amount;
        
        // 验证不变量
        assert(totalDeposits >= deposits[msg.sender]);
        assert(totalDeposits >= amount);
    }
    
    // 5. 优雅降级
    address public backupOracle;
    address public primaryOracle;
    bool public usingBackup;
    
    function getPrice() public returns (uint256) {
        (bool success, uint256 price) = primaryOracle.call("");
        
        if (!success) {
            // 降级到备份
            if (!usingBackup) {
                usingBackup = true;
            }
            
            (success, price) = backupOracle.call("");
            require(success, "Both oracles failed");
        }
        
        return price;
    }
}

4.4 测试错误处理

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import "../src/CustomErrors.sol";

contract CustomErrorsTest is Test {
    CustomErrors public contract_;
    
    function setUp() public {
        contract_ = new CustomErrors();
    }
    
    // 测试自定义错误被正确抛出
    function testInsufficientBalance() public {
        // 先存款
        contract_.deposit{value: 1 ether}();
        
        // 尝试取出更多
        vm.expectRevert(CustomErrors.InsufficientBalance.selector);
        contract_.withdraw(2 ether);
    }
    
    // 测试错误消息(如果使用字符串)
    function testRevertWithMessage() public {
        // 某些情况下需要测试具体消息
        vm.expectRevert("Amount is zero");
        contract_.withdraw(0);
    }
    
    // 测试panic
    function testAssert() public {
        uint256[] memory arr = new uint256[](5);
        
        // 数组越界会触发 Panic
        vm.expectRevert(stdError.arithError);
        arr[10]; // 故意越界
    }
    
    // 测试复杂错误数据
    function testCustomErrorWithData() public {
        vm.expectRevert(abi.encodeWithSelector(
            CustomErrors.InsufficientBalance.selector,
            1 ether,
            5 ether
        ));
        contract_.withdraw(5 ether);
    }
}

五、综合实战案例

5.1 安全的代币合约错误处理

solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// 完整的错误处理最佳实践
contract SecureToken {
    // ========== 自定义错误 ==========
    error TransferFromZero();
    error TransferToZero();
    error TransferExceedsBalance(address from, uint256 balance, uint256 amount);
    error TransferExceedsAllowance(address spender, uint256 allowance, uint256 amount);
    error ApproveToZeroAddress();
    error BurnExceedsBalance(uint256 balance, uint256 amount);
    
    // ========== 事件 ==========
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    // =========== 状态 ==========
    string public name;
    string public symbol;
    uint8 public decimals;
    uint256 public totalSupply;
    
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    
    // ========== 构造函数 ==========
    constructor(string memory _name, string memory _symbol, uint8 _decimals) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
    }
    
    // ========== 核心函数与错误处理 ==========
    function _transfer(address from, address to, uint256 amount) internal {
        // 前置条件验证
        if (from == address(0)) revert TransferFromZero();
        if (to == address(0)) revert TransferToZero();
        if (amount == 0) revert TransferToZero(); // 可选:零转账无意义
        
        // 余额检查
        uint256 fromBalance = balanceOf[from];
        if (fromBalance < amount) {
            revert TransferExceedsBalance(from, fromBalance, amount);
        }
        
        // 状态更新
        balanceOf[from] = fromBalance - amount;
        balanceOf[to] += amount;
        
        emit Transfer(from, to, amount);
    }
    
    function transfer(address to, uint256 amount) public returns (bool) {
        _transfer(msg.sender, to, amount);
        return true;
    }
    
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public returns (bool) {
        // 授权检查
        uint256 spenderAllowance = allowance[from][msg.sender];
        if (spenderAllowance != type(uint256).max) {
            if (spenderAllowance < amount) {
                revert TransferExceedsAllowance(
                    msg.sender, 
                    spenderAllowance, 
                    amount
                );
            }
            allowance[from][msg.sender] = spenderAllowance - amount;
        }
        
        _transfer(from, to, amount);
        return true;
    }
    
    function approve(address spender, uint256 amount) public returns (bool) {
        if (spender == address(0)) revert ApproveToZeroAddress();
        
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }
    
    // ========== 内部函数的不变量检查 ==========
    function _mint(address to, uint256 amount) internal {
        if (to == address(0)) revert TransferToZero();
        if (amount == 0) revert TransferToZero(); // 可选
        
        uint256 oldTotalSupply = totalSupply;
        uint256 oldBalance = balanceOf[to];
        
        totalSupply += amount;
        balanceOf[to] += amount;
        
        // 验证不变量
        assert(totalSupply > oldTotalSupply);
        assert(balanceOf[to] > oldBalance);
        assert(totalSupply >= balanceOf[to]);
    }
    
    function _burn(address from, uint256 amount) internal {
        if (from == address(0)) revert TransferFromZero();
        
        uint256 fromBalance = balanceOf[from];
        if (fromBalance < amount) {
            revert BurnExceedsBalance(fromBalance, amount);
        }
        
        uint256 oldTotalSupply = totalSupply;
        
        balanceOf[from] = fromBalance - amount;
        totalSupply -= amount;
        
        // 验证不变量
        assert(totalSupply < oldTotalSupply);
        assert(totalSupply >= balanceOf[from]);
    }
}

总结

深入理解Solidity的错误处理机制对于编写安全、高效的智能合约至关重要。

特性requirerevertassert
用途输入/状态验证复杂条件验证不变量检查
底层操作码REVERTREVERTINVALID
Gas退还否(消耗全部)
错误消息可选可选
自定义错误支持支持不支持
适用场景日常验证复杂逻辑内部检查

核心最佳实践

  1. 优先使用自定义错误 – 比字符串节省Gas,且更精确
  2. require用于验证 – 输入、状态、外部调用结果
  3. revert用于复杂逻辑 – 多条件判断、复杂状态机
  4. assert用于不变量 – 内部一致性检查
  5. 总是验证外部调用 – 不要假设外部合约总是成功
  6. 测试错误路径 – 确保每个错误条件都被正确触发

相关推荐

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注