Wake 调试指南:Python 驱动的 Solidity 测试

  • Ackee
  • 发布于 9小时前
  • 阅读 17

本文介绍了使用 Wake 工具进行智能合约调试的各种技术,包括利用 Solidity 中的 console logging 库、手动引导 Fuzzing(MGF) 技术、使用 ipdb 进行交互式调试以及缩小失败序列,同时还提供了一些额外的技巧,如处理预期 revert、性能分析、Fuzzing 覆盖率和跨链测试,旨在帮助开发者更高效地定位和解决智能合约中的问题。

当模糊测试运行失败或合约行为让你感到惊讶时,有效的调试可以将数小时的挫败感转化为几分钟的集中调查。Wake 为你提供了 Python 完整的调试生态系统,并结合了 Solidity 专用的可见性工具。本指南向你展示了 Ackee 的审计团队用来快速隔离问题的技术。

合约状态的可见性

调试任何失败测试的第一步是了解你的合约实际上在做什么。Wake 提供了几种暴露内部状态的方法。

在 Solidity 中进行控制台日志记录

Wake 包含一个可以直接在你的 Solidity 代码中使用的控制台日志库:

import "wake/console.sol";

你可以使用 console.log()console.logBytes32()console.logBytes() 在合约执行期间内联记录值。该库包括用于动态长度数据的变体。这为你提供了 printf 风格的调试,而无需部署到实时网络或设置复杂的工具。

当你需要可读的调用跟踪时,使用 account.label 将原始地址替换为有意义的名称。你将在跟踪中看到 “TokenContract” 或 “UserWallet”,而不是 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb

对于纯函数,你需要一种变通方法,因为它们在正常执行期间无法发出日志。快速的解决方法是暂时将该函数和任何依赖的纯函数标记为 view。如果这不可行,则发出一个自定义事件来代替。

手动指导的模糊测试 (MGF) 技术

Wake 基于 Python 的模糊测试为你提供了覆盖率指导的方法无法比拟的控制。关键是维护准确的 Python 状态,以反映你的合约状态。

不变性函数

编写比流程函数更多的不变性函数。不变性验证你的 Python 状态与链上现实保持同步,这在调试复杂的故障时变得至关重要。当测试失败时,你可以信任你的 Python 状态来了解出了什么问题。

找出导致失败的流程

要确定哪个流程导致了失败,请在每个流程的开头添加日志记录:

def pre_flow(self, flow: Callable):
    logger.info(f"[FLOW] {flow.__name__}")

这将创建一个面包屑轨迹,显示导致失败的确切操作顺序。

使用 ipdb 进行交互式调试

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” 以可视化你的测试执行了哪些合约代码。

Token 测试模式

测试 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ackee
Ackee
Cybersecurity experts | We audit Ethereum and Solana | Creators of @WakeFramework , Solidity (Wake) & @TridentSolana | Educational partner of Solana Foundation