ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WTF Solidity 极简入门:用 try-catch 优雅处理智能合约异常

WTF Solidity 极简入门:用 try-catch 优雅处理智能合约异常 WTF Solidity 极简入门用 try-catch 优雅处理智能合约异常【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Soliditytry-catch是 Solidity 0.6 引入的标准异常处理机制用于捕获外部合约调用与合约创建过程中的失败。本篇教程将带你掌握try-catch的完整语法、三种catch分支Error、Panic、bytes的适用场景并通过OnlyEven实战合约在 Remix 中逐一验证外部函数调用与合约创建两种异常处理流程同时指出try-catch也无法捕获的两类边界情况。为什么需要 try-catch以及它能用在哪些场景异常处理是现代编程语言的标配能力try-catch于 Solidity 0.6 版本被引入为智能合约提供了一套标准的尝试执行—失败捕获结构。在智能合约中最容易出错的环节恰恰是跨合约交互被调合约可能因为参数不合法、内部状态异常、Gas 不足等原因revert如果调用方不做处理整个交易都会回滚。在 Solidity 中try-catch只能用于external函数调用或创建合约时的constructor调用被视为external调用其基本语法如下try externalContract.f() { // call成功的情况下 运行一些代码 } catch { // call失败的情况下 运行一些代码 }其中externalContract.f()是某个外部合约的函数调用try模块在调用成功时运行catch模块在调用失败时运行。需要特别说明的几点this.f()同样适用this.f()也会被视作一次外部调用因此同样可以放在try后面。但它不能在构造函数中使用因为此时合约尚未创建完成。内部调用无法捕获try-catch只能拦截外部调用层的失败合约内部函数之间的调用internal call抛出的异常不会被其捕获。返回值必须显式声明如果被调函数有返回值必须在try之后声明returns(returnType val)且该变量只能在try模块内使用如果是创建合约返回的则是新创建的合约变量try externalContract.f() returns(returnType val){ // call成功的情况下 运行一些代码 } catch { // call失败的情况下 运行一些代码 }三种 catch 分支按异常原因精准分类处理除了无参数的catch {}之外catch模块还支持按异常原因分类捕获这在 Solidity 0.8 及以后版本中尤其重要0.8 起算术溢出由Panic而非require抛出try externalContract.f() returns(returnType){ // call成功的情况下 运行一些代码 } catch Error(string memory /*reason*/) { // 捕获 revert(reasonString) 和 require(false, reasonString) } catch Panic(uint /*errorCode*/) { // 捕获 Panic 导致的错误例如 assert 失败、溢出、除零、数组访问越界 } catch (bytes memory /*lowLevelData*/) { // 发生了 revert但上面两个异常类型都匹配失败时进入该分支 // 例如 revert()、require(false)、revert 自定义类型的 error }各分支与异常类型的对应关系可以汇总如下catch 分支捕获的异常来源典型触发方式catch Error(string memory reason)Error(string)类型异常revert(reasonString)、require(false, reasonString)catch Panic(uint errorCode)Panic(uint256)类型异常assert失败、算术溢出、除零、数组越界访问catch (bytes memory data)其余所有底层异常数据revert()无参回滚、require(false)、自定义错误类型custom error实战构造一个会抛异常的 OnlyEven 合约为了演示try-catch我们首先构造一个故意制造异常的外部合约OnlyEven完整实现见 Languages/en/30_TryCatch_en/TryCatch.sol// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; contract OnlyEven{ constructor(uint a){ require(a ! 0, invalid number); assert(a ! 1); } function onlyEven(uint256 b) external pure returns(bool success){ // revert when an odd number is entered require(b % 2 0, Ups! Reverting); success true; } }OnlyEven合约包含一个构造函数和一个onlyEven函数构造函数接收参数a当a 0时require(a ! 0, invalid number)抛出Error(string)异常当a 1时assert(a ! 1)抛出Panic(uint256)异常其他取值均正常。onlyEven函数接收参数b当b为奇数时require(b % 2 0, Ups! Reverting)抛出异常。场景一处理外部函数调用的异常接下来在TryCatch合约中声明事件与状态变量并在构造函数中创建OnlyEven实例// 成功 event event SuccessEvent(); // 失败 event event CatchEvent(string message); event CatchByte(bytes data); // 声明 OnlyEven 合约变量 OnlyEven even; constructor() { even new OnlyEven(2); }三个事件的职责非常清晰SuccessEvent在调用成功时释放CatchEvent与CatchByte在抛出异常时释放分别对应require/revertError与assertPanic→ 落入bytes分支的异常情况。even是OnlyEven合约类型的状态变量。随后在execute函数中用try-catch包裹对onlyEven的外部调用对应源码 Languages/en/30_TryCatch_en/TryCatch.sol// 在 external call 中使用 try-catch function execute(uint amount) external returns (bool success) { try even.onlyEven(amount) returns(bool _success){ // call成功的情况下 emit SuccessEvent(); return _success; } catch Error(string memory reason){ // call不成功的情况下 emit CatchEvent(reason); } }在 Remix 上验证 execute运行execute(0)0为偶数满足require(b % 2 0, Ups! Reverting)无异常抛出调用成功并释放SuccessEvent。运行execute(1)1为奇数不满足require条件onlyEven抛出Error(Ups! Reverting)被catch Error(string memory reason)捕获释放CatchEvent并携带原因字符串。场景二处理合约创建的异常创建合约同样被视为外部调用因此可以把try模块直接改写为new OnlyEven(a)捕获构造函数中抛出的异常对应源码 Languages/en/30_TryCatch_en/TryCatch.sol// 在创建新合约中使用 try-catch 合约创建被视为 external call // executeNew(0) 会失败并释放 CatchEvent // executeNew(1) 会失败并释放 CatchByte // executeNew(2) 会成功并释放 SuccessEvent function executeNew(uint a) external returns (bool success) { try new OnlyEven(a) returns(OnlyEven _even){ // call成功的情况下 emit SuccessEvent(); success _even.onlyEven(a); } catch Error(string memory reason) { // catch 失败的 revert() 和 require() emit CatchEvent(reason); } catch (bytes memory reason) { // catch 失败的 assert() emit CatchByte(reason); } }在 Remix 上验证 executeNew这里的关键观察点是不同异常类型会落入不同分支运行executeNew(0)0不满足require(a ! 0, invalid number)抛出Error(string)被第一个分支捕获释放CatchEvent。运行executeNew(1)1不满足assert(a ! 1)。注意assert失败的错误类型是Panic(uint256)而非Error(string)因此不会匹配catch Error而是落入catch (bytes memory reason)分支释放CatchByte——这正是演示三种分支差异的核心用例。运行executeNew(2)2同时满足require与assert条件创建成功释放SuccessEvent并继续调用新合约的onlyEven(2)返回true。边界try-catch 也无法捕获的两类情况try-catch并非万能。仓库的中文版教程 30_TryCatch/readme.md 及其扩展源码 30_TryCatch/TryCatch.sol 补充了两种即使包上try-catch也会导致整个交易revert的边界场景非常值得警惕1. 调用非合约地址的方法codesize 为 0当try中调用的是一个没有代码的地址如将address(0)强制转换为合约类型调用本身会因目标无代码而失败且这种失败无法被try-catch拦截整个交易直接回滚对应 30_TryCatch/TryCatch.sol// 调用非合约地址的方法这里无法被 try-catch 捕获会导致 revert function executeRevert() external { // 将 0 地址强制转换为 OnlyEven 合约地址模拟调用非合约地址的方法 try OnlyEven(address(0)).onlyEven(1){ // call成功的情况下 emit SuccessEvent(); } catch Error(string memory reason){ emit CatchEvent(reason); } catch (bytes memory reason){ emit CatchByte(reason); } }2. 返回值与预期不一致当被调函数实际返回类型与try后声明的returns类型不匹配时解码阶段就会失败同样无法被try-catch捕获。仓库源码中的OnlyEven2的onlyEven返回uint256而调用方按bool期望解码于是整体revert对应 30_TryCatch/TryCatch.sol// 调用返回值不一致的方法这里无法被 try-catch 捕获会导致 revert function executeRevert2() external returns (bool success){ address onlyEven2 address(new OnlyEven2(2)); uint256 amount 2; // OnlyEven2 的返回值是 uint256而这里期望的是 bool会导致 revert try OnlyEven(onlyEven2).onlyEven(amount) returns (bool _success){ // call成功的情况下 emit SuccessEvent(); return _success; } catch Error(string memory reason){ // call不成功的情况下 emit CatchEvent(reason); } }这提醒开发者try-catch保护的是被调函数内部业务逻辑抛出的异常而无法兜底调用目标本身不合法或ABI 解码不一致这类调用层错误。总结通过本篇教程我们掌握了 Solidity 中try-catch的完整用法适用范围有限只能用于外部合约调用和合约创建constructorthis.f()同样视为外部调用但不能出现在构造函数中。返回值规则若try执行成功返回变量必须显式声明且类型必须与被调函数实际返回类型一致。分支优先级catch Error(string)捕获revert(reason)/require(false, reason)catch Panic(uint)捕获assert、溢出、除零、越界等Panic异常catch (bytes)捕获其余所有底层回滚裸revert()、require(false)、自定义错误。两个盲区调用非合约地址codesize 为 0的方法、返回值与returns声明不一致这两种情况try-catch无法捕获会直接导致交易回滚。完整的可运行示例请直接查看 Languages/en/30_TryCatch_en/TryCatch.sol含外部调用与合约创建两种场景边界情况的扩展演示见 30_TryCatch/TryCatch.sol将其粘贴到 Remix 中即可复现本文全部验证过程。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表