异常处理 #
一、异常处理概述 #
Solidity提供了三种异常处理方式:
| 方式 | 用途 | Gas退还 | 错误类型 |
|---|---|---|---|
| require | 输入验证、条件检查 | 是 | Error(string) |
| assert | 内部不变量检查 | 否 | Panic(uint256) |
| revert | 复杂条件、自定义错误 | 是 | Error/自定义 |
二、require #
2.1 基本用法 #
solidity
contract RequireDemo {
// 基本验证
function divide(uint256 a, uint256 b) public pure returns (uint256) {
require(b != 0, "Division by zero");
return a / b;
}
// 地址验证
function transfer(address to, uint256 amount) public pure {
require(to != address(0), "Invalid recipient");
require(amount > 0, "Amount must be positive");
}
// 余额验证
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
2.2 使用场景 #
solidity
contract RequireUseCases {
address public owner;
bool public paused;
mapping(address => uint256) public balances;
constructor() {
owner = msg.sender;
}
// 1. 输入验证
function setValue(uint256 value) public pure {
require(value > 0, "Value must be positive");
require(value <= 100, "Value too large");
}
// 2. 权限检查
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// 3. 状态检查
modifier whenNotPaused() {
require(!paused, "Contract paused");
_;
}
// 4. 外部调用结果检查
function callExternal(address target, bytes memory data) public returns (bool) {
(bool success, bytes memory result) = target.call(data);
require(success, "External call failed");
return abi.decode(result, (bool));
}
// 5. 时间检查
function claim() public view {
require(block.timestamp >= 1700000000, "Too early");
}
}
2.3 require无消息 #
solidity
contract RequireNoMessage {
// 可以省略错误消息
function check(bool condition) public pure {
require(condition); // 失败时显示空字符串
}
// 推荐:总是提供有意义的错误消息
function checkBetter(bool condition) public pure {
require(condition, "Condition not met");
}
}
三、assert #
3.1 基本用法 #
solidity
contract AssertDemo {
uint256 public constant MAX_SUPPLY = 1000000;
uint256 public totalSupply;
// 断言不变量
function mint(uint256 amount) public {
totalSupply += amount;
assert(totalSupply <= MAX_SUPPLY); // 不应该超过最大供应量
}
// 断言内部一致性
mapping(address => uint256) public balances;
uint256 public totalBalance;
function deposit() public payable {
balances[msg.sender] += msg.value;
totalBalance += msg.value;
// 断言内部一致性
assert(address(this).balance == totalBalance);
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
totalBalance -= amount;
payable(msg.sender).transfer(amount);
// 断言内部一致性
assert(address(this).balance == totalBalance);
}
}
3.2 assert触发Panic错误 #
solidity
contract PanicErrors {
// Panic错误代码
// 0x00: 保留
// 0x01: assert失败
// 0x11: 算术溢出(0.7.x及以下)
// 0x12: 除以零或取余零
// 0x21: 越界访问数组
// 0x22: 访问负索引数组
// 0x31: 调用空函数
// 0x32: 类型转换失败
// 0x41: 存储空间不足
function demonstratePanic() public pure {
assert(false); // 触发Panic(0x01)
}
}
3.3 require vs assert #
solidity
contract RequireVsAssert {
mapping(address => uint256) public balances;
uint256 public totalSupply;
// require:验证外部输入
function transfer(address to, uint256 amount) public {
require(to != address(0), "Invalid recipient"); // 外部输入验证
require(balances[msg.sender] >= amount, "Insufficient balance"); // 外部状态验证
balances[msg.sender] -= amount;
balances[to] += amount;
}
// assert:验证内部不变量
function internalCheck() public view {
uint256 sum = 0;
// 假设我们遍历所有余额...
assert(sum == totalSupply); // 内部不变量
}
}
四、revert #
4.1 基本用法 #
solidity
contract RevertDemo {
// 简单revert
function simpleRevert() public pure {
revert("Something went wrong");
}
// 条件revert
function conditionalRevert(bool shouldRevert) public pure {
if (shouldRevert) {
revert("Condition triggered revert");
}
}
// 复杂条件
function complexCondition(
uint256 a,
uint256 b,
uint256 c
) public pure returns (uint256) {
if (a == 0) {
revert("a cannot be zero");
}
if (b == 0) {
revert("b cannot be zero");
}
if (c == 0) {
revert("c cannot be zero");
}
return (a / b) + c;
}
}
4.2 revert vs require #
solidity
contract RevertVsRequire {
// 使用require
function withRequire(uint256 value) public pure {
require(value > 0, "Value must be positive");
require(value < 100, "Value too large");
require(value % 2 == 0, "Value must be even");
}
// 使用revert(复杂条件)
function withRevert(uint256 value) public pure {
if (value <= 0) {
revert("Value must be positive");
}
if (value >= 100) {
revert("Value too large");
}
if (value % 2 != 0) {
revert("Value must be even");
}
}
// 混合使用
function mixed(uint256 value) public pure {
require(value > 0, "Value must be positive");
if (value >= 100) {
revert("Value too large");
}
}
}
五、自定义错误 #
5.1 定义错误 #
solidity
contract CustomErrors {
// 定义错误
error InsufficientBalance(uint256 available, uint256 required);
error InvalidAddress(address addr);
error TransferFailed();
error Unauthorized(address caller);
error ContractPaused();
mapping(address => uint256) public balances;
address public owner;
bool public paused;
constructor() {
owner = msg.sender;
}
}
5.2 使用自定义错误 #
solidity
contract CustomErrorsUsage {
error InsufficientBalance(uint256 available, uint256 required);
error InvalidAddress(address addr);
error Unauthorized(address caller);
mapping(address => uint256) public balances;
address public owner;
constructor() {
owner = msg.sender;
}
// 使用自定义错误
function withdraw(uint256 amount) public {
if (balances[msg.sender] < amount) {
revert InsufficientBalance(balances[msg.sender], amount);
}
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
// 权限检查
modifier onlyOwner() {
if (msg.sender != owner) {
revert Unauthorized(msg.sender);
}
_;
}
// 地址验证
function transfer(address to, uint256 amount) public {
if (to == address(0)) {
revert InvalidAddress(to);
}
// 转账逻辑
}
}
5.3 自定义错误优势 #
solidity
contract CustomErrorAdvantages {
// 传统方式:字符串错误(消耗更多Gas)
function traditionalError(uint256 value) public pure {
require(value > 0, "Value must be greater than zero");
}
// 自定义错误:更省Gas
error ValueMustBePositive();
function customError(uint256 value) public pure {
if (value <= 0) {
revert ValueMustBePositive();
}
}
// 带参数的自定义错误
error ValueOutOfRange(uint256 value, uint256 min, uint256 max);
function withParameters(uint256 value) public pure {
if (value < 10 || value > 100) {
revert ValueOutOfRange(value, 10, 100);
}
}
}
六、try/catch #
6.1 基本用法 #
solidity
interface IExternalContract {
function someFunction(uint256 value) external returns (uint256);
}
contract TryCatchDemo {
IExternalContract public externalContract;
event OperationSuccess(uint256 result);
event OperationFailed(string reason);
event OperationFailedBytes(bytes data);
constructor(address _contract) {
externalContract = IExternalContract(_contract);
}
function callExternal(uint256 value) public returns (uint256) {
try externalContract.someFunction(value) returns (uint256 result) {
emit OperationSuccess(result);
return result;
} catch Error(string memory reason) {
// 捕获require/revert错误
emit OperationFailed(reason);
return 0;
} catch (bytes memory data) {
// 捕获其他错误(包括自定义错误)
emit OperationFailedBytes(data);
return 0;
}
}
}
6.2 try/catch示例 #
solidity
contract TryCatchExample {
struct Result {
bool success;
uint256 value;
string errorMessage;
}
ExternalContract public target;
constructor(address _target) {
target = ExternalContract(_target);
}
function safeCall(uint256 value) public returns (Result memory) {
try target.process(value) returns (uint256 result) {
return Result(true, result, "");
} catch Error(string memory reason) {
return Result(false, 0, reason);
} catch {
return Result(false, 0, "Unknown error");
}
}
}
contract ExternalContract {
function process(uint256 value) external pure returns (uint256) {
require(value > 0, "Value must be positive");
require(value < 100, "Value too large");
return value * 2;
}
}
6.3 try/catch限制 #
solidity
contract TryCatchLimitations {
// try/catch只能用于外部调用
// 不能用于内部函数调用
function internalCall() public pure returns (uint256) {
// 错误:不能对内部函数使用try/catch
// try internalFunction() { } catch { }
return internalFunction();
}
function internalFunction() internal pure returns (uint256) {
require(false, "Always fails");
return 0;
}
// 只能用于external调用
ExternalContract public external;
function externalCall() public returns (uint256) {
try external.process() returns (uint256 result) {
return result;
} catch {
return 0;
}
}
}
七、实际应用示例 #
7.1 安全转账 #
solidity
contract SafeTransfer {
error TransferFailed(address to, uint256 amount);
error InsufficientBalance(uint256 available, uint256 required);
mapping(address => uint256) public balances;
event Transfer(address indexed from, address indexed to, uint256 amount);
function transfer(address to, uint256 amount) public {
if (balances[msg.sender] < amount) {
revert InsufficientBalance(balances[msg.sender], amount);
}
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
}
function withdraw(uint256 amount) public {
if (balances[msg.sender] < amount) {
revert InsufficientBalance(balances[msg.sender], amount);
}
balances[msg.sender] -= amount;
(bool success, ) = payable(msg.sender).call{value: amount}("");
if (!success) {
revert TransferFailed(msg.sender, amount);
}
}
receive() external payable {
balances[msg.sender] += msg.value;
}
}
7.2 访问控制 #
solidity
contract AccessControl {
error Unauthorized(address caller, string role);
error ContractPaused();
address public owner;
mapping(address => bool) public admins;
bool public paused;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
if (msg.sender != owner) {
revert Unauthorized(msg.sender, "owner");
}
_;
}
modifier onlyAdmin() {
if (!admins[msg.sender] && msg.sender != owner) {
revert Unauthorized(msg.sender, "admin");
}
_;
}
modifier whenNotPaused() {
if (paused) {
revert ContractPaused();
}
_;
}
function setAdmin(address user, bool status) public onlyOwner {
admins[user] = status;
}
function setPaused(bool _paused) public onlyAdmin {
paused = _paused;
}
function sensitiveOperation() public onlyAdmin whenNotPaused {
// 敏感操作
}
}
7.3 代币合约 #
solidity
contract Token {
error InsufficientBalance(uint256 available, uint256 required);
error InsufficientAllowance(uint256 available, uint256 required);
error InvalidAddress(address addr);
error ZeroAmount();
string public name = "MyToken";
string public symbol = "MTK";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(uint256 _totalSupply) {
totalSupply = _totalSupply;
balanceOf[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}
function transfer(address to, uint256 amount) public returns (bool) {
if (to == address(0)) revert InvalidAddress(to);
if (amount == 0) revert ZeroAmount();
if (balanceOf[msg.sender] < amount) {
revert InsufficientBalance(balanceOf[msg.sender], amount);
}
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) public returns (bool) {
if (spender == address(0)) revert InvalidAddress(spender);
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
if (from == address(0)) revert InvalidAddress(from);
if (to == address(0)) revert InvalidAddress(to);
if (amount == 0) revert ZeroAmount();
if (balanceOf[from] < amount) {
revert InsufficientBalance(balanceOf[from], amount);
}
if (allowance[from][msg.sender] < amount) {
revert InsufficientAllowance(allowance[from][msg.sender], amount);
}
balanceOf[from] -= amount;
balanceOf[to] += amount;
allowance[from][msg.sender] -= amount;
emit Transfer(from, to, amount);
return true;
}
}
八、最佳实践 #
8.1 选择正确的异常处理方式 #
solidity
contract BestPractices {
// require:验证外部输入和条件
function validateInput(uint256 value) public pure {
require(value > 0, "Value must be positive");
}
// assert:验证内部不变量
function checkInvariant() public view {
assert(address(this).balance >= 0); // 永远为真
}
// revert:复杂条件或自定义错误
function complexValidation(uint256 value) public pure {
if (value <= 0) {
revert("Value must be positive");
}
if (value > 100) {
revert("Value too large");
}
}
// 自定义错误:节省Gas
error InvalidValue(uint256 value);
function withCustomError(uint256 value) public pure {
if (value <= 0 || value > 100) {
revert InvalidValue(value);
}
}
}
8.2 提供有意义的错误消息 #
solidity
contract MeaningfulErrors {
// 不推荐:模糊的错误消息
function bad() public pure {
require(false, "Error");
}
// 推荐:明确的错误消息
function good() public pure {
require(false, "Transfer failed: insufficient balance");
}
// 推荐:使用自定义错误
error TransferFailed(address from, address to, uint256 amount, string reason);
}
8.3 错误处理模式 #
solidity
contract ErrorPatterns {
// 模式1:提前检查
function earlyCheck(uint256 value) public pure returns (uint256) {
require(value > 0, "Value must be positive");
require(value < 100, "Value too large");
return value * 2;
}
// 模式2:检查-生效-交互
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public {
// 检查
require(balances[msg.sender] >= amount, "Insufficient balance");
// 生效
balances[msg.sender] -= amount;
// 交互
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
}
}
九、总结 #
异常处理要点:
| 方式 | 用途 | Gas退还 | 推荐场景 |
|---|---|---|---|
| require | 条件验证 | 是 | 输入验证、权限检查 |
| assert | 不变量检查 | 否 | 内部一致性检查 |
| revert | 复杂条件 | 是 | 复杂逻辑、自定义错误 |
| 自定义错误 | 节省Gas | 是 | 生产环境 |
使用建议:
- 使用require验证外部输入
- 使用assert验证内部不变量
- 使用自定义错误节省Gas
- 提供有意义的错误消息
- 遵循检查-生效-交互模式
接下来,让我们学习函数!
最后更新:2026-03-27