本文介绍了使用 Wake 工具进行智能合约调试的各种技术,包括利用 Solidity 中的 console logging 库、手动引导 Fuzzing(MGF) 技术、使用 ipdb 进行交互式调试以及缩小失败序列,同时还提供了一些额外的技巧,如处理预期 revert、性能分析、Fuzzing 覆盖率和跨链测试,旨在帮助开发者更高效地定位和解决智能合约中的问题。
当模糊测试运行失败或合约行为让你感到惊讶时,有效的调试可以将数小时的挫败感转化为几分钟的集中调查。Wake 为你提供了 Python 完整的调试生态系统,并结合了 Solidity 专用的可见性工具。本指南向你展示了 Ackee 的审计团队用来快速隔离问题的技术。
调试任何失败测试的第一步是了解你的合约实际上在做什么。Wake 提供了几种暴露内部状态的方法。
Wake 包含一个可以直接在你的 Solidity 代码中使用的控制台日志库:
import "wake/console.sol";
你可以使用 console.log()、console.logBytes32() 和 console.logBytes() 在合约执行期间内联记录值。该库包括用于动态长度数据的变体。这为你提供了 printf 风格的调试,而无需部署到实时网络或设置复杂的工具。
当你需要可读的调用跟踪时,使用 account.label 将原始地址替换为有意义的名称。你将在跟踪中看到 “TokenContract” 或 “UserWallet”,而不是 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb。
对于纯函数,你需要一种变通方法,因为它们在正常执行期间无法发出日志。快速的解决方法是暂时将该函数和任何依赖的纯函数标记为 view。如果这不可行,则发出一个自定义事件来代替。
Wake 基于 Python 的模糊测试为你提供了覆盖率指导的方法无法比拟的控制。关键是维护准确的 Python 状态,以反映你的合约状态。
编写比流程函数更多的不变性函数。不变性验证你的 Python 状态与链上现实保持同步,这在调试复杂的故障时变得至关重要。当测试失败时,你可以信任你的 Python 状态来了解出了什么问题。
要确定哪个流程导致了失败,请在每个流程的开头添加日志记录:
def pre_flow(self, flow: Callable):
logger.info(f"[FLOW] {flow.__name__}")
这将创建一个面包屑轨迹,显示导致失败的确切操作顺序。
Wake 与 Python 的调试器无缝集成。当你需要检查执行中的状态或单步执行事务序列时,请进入 ipdb。
要检查最近的事务,请使用 chain.txs[-1].call_trace 获取最近的事务,或使用 chain.txs[-2].call_trace 获取之前的事务。这使你可以完全了解链上发生的事情。
> chain.txs[-1].call_trace
> chain.txs[-2].call_trace
对于系统调试,设置在出现问题时自动打印跟踪的处理程序:
def revert_handler(e: RevertError):
if e.tx is not None:
print(e.tx.call_trace)
def tx_callback_fn(tx: TransactionAbc) -> None:
print(tx.call_trace)
# so no print(tx.call_trace) everywhere, only for debug because too slow
@chain.connect()
@on_revert(revert_handler)
def test_fuzz_stethpool():
chain.tx_callback = tx_callback_fn
FuzzVWStEth().run(1, 100000)
请注意,为每个事务打印跟踪会显着降低执行速度,因此仅在主动调试时才启用 tx_callback。
当你发现失败时,请使用完全相同的随机种子重现它:
wake test tests/test_counter_fuzz.py -S62061e838798ad0f -d -v
-d 标志使你进入调试器,-v 提高详细程度,并且种子确保你每次都遇到相同的序列。
一旦你找到了失败的序列,缩小会隔离触发 bug 的最小流程组合。这确认了问题是出现在序列的早期还是需要完整的设置。
使用 wake test tests/test_something.py -SH 运行缩小以缩小到人类可读的序列,或使用 -SR 缩小到紧凑的表示形式。较小的序列通常比完整的失败更清楚地揭示核心问题。
wake test tests/test_something.py -SH
wake test tests/test_something.py -SR
在测试边缘情况时,你通常需要验证操作在特定条件下是否会回滚。Wake 的 may_revert 上下文管理器使你可以断言成功执行和预期失败:
with may_revert() as e:
tx = contract.operation()
if condition_that_causes_revert:
assert e.value == Contract.ExpectedError()
return "expected_revert_reason"
assert e.value is None # Must succeed otherwise
这种模式使测试具有自我记录性——可以立即清楚地知道哪些路径应该回滚,哪些路径应该成功。
当测试运行缓慢时,对它们进行分析以找到瓶颈:
wake --profile test tests/test_fuzz.py
这将生成一个配置文件,你可以使用以下命令对其进行可视化:
gprof2dot -f pstats .wake/wake.prof | dot -Tsvg -o wake.prof.svg
生成的 SVG 确切地显示了执行时间的去向,从而使优化目标变得显而易见。
要了解测试覆盖率,请运行:
wake test tests/test_fuzz.py --coverage
这将在你的项目根目录中生成 coverage.cov。打开 VS Code 的命令面板并选择 “Wake: Show Coverage” 以可视化你的测试执行了哪些合约代码。
测试 DeFi 协议需要铸造 token 并管理授权。Wake 使这变得简单明了:
## Mint directly to your test user
mint_erc20(token, user, 100 * 10**18)
token.approve(contract, 100 * 10**18, from_=user)
contract.pullToken(from_=user)
对于跨链场景,Wake 允许你同时运行多个独立的链:
from wake.testing import Chain
## Create two separate blockchain instances
ethereum_chain = Chain()
polygon_chain = Chain()
@ethereum_chain.connect()
@polygon_chain.connect()
def test_cross_chain_transfer():
# Both chains are now connected and ready
pass
首先使用清晰的不变性来验证你对合约状态的假设。通过处理程序和回调保持调用跟踪易于访问。当测试失败时,在深入研究之前将它们缩小到最小的可重现程序。这些模式使花费数小时寻找 bug 和在几分钟内隔离它们之间有所不同。
此处的技巧利用了 Python 成熟的调试生态系统,同时为你提供了对 EVM 内部结构的直接访问。Wake 将 Python 的表达能力与智能合约安全性所需的精度相结合。这是快速开发和全面测试的基础。
- 原文链接: ackee.xyz/blog/wake-debu...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!