使用hardhat-tracer在本地追踪智能合约交易调用和事件

本文介绍了hardhat-tracer工具,它可以帮助开发者在测试、学习智能合约协议或分支时追踪智能合约事务。文章通过一个简单的ERC20合约示例,展示了如何使用hardhat-tracer的trace flag来追踪函数调用,并展示了如何添加地址名称标签以提高可读性。hardhat-tracer在处理复杂的代码(如大型项目分支)时非常有用,可以帮助理解代码层面上发生的事情。

使用 hardhat-tracer 在本地跟踪智能合约的交易调用和事件。

你是否曾注意到在测试或学习智能合约协议或 fork 时需要跟踪智能合约交易的需求?

hardhat-tracer 可以帮助你。

在这里找到这个工具:https://github.com/zemse/hardhat-tracer

从 Readme 文件中,可以很清楚地了解如何使用该工具。但是,我仍然想通过一些简单的合约示例来演示该工具的用法。

在本示例中,我们将看到 trace 标志,但它也提供其他标志,如 fulltracehash 用于跟踪。


以下是我们使用的合约:

  1. 简单的 ERC20 合约,用于 mint 给定的代币供应量:
    
    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.4;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol";

contract ERC20Mock is ERC20, Ownable { constructor( string memory name, string memory symbol, uint256 supply ) public ERC20(name, symbol) { _mint(msg.sender, supply); } }


2\. OtherContract(一个调用 EventEmitter 合约来 emit 事件的合约):
```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./EventEmitter.sol";

contract OtherContract{
    address emitter;

    function takeTokenAndCallOtherFunction(address _tokenAddr) public {
        IERC20(_tokenAddr).transferFrom(msg.sender, address(this), 5 ether);
        emitTokensTaken(msg.sender);
    }

    function setEmitterAddress(address _emitter) external {
        emitter=_emitter;
    }

    function emitTokensTaken(address _from) private {
        EventEmitter(emitter).emitTokenFrom(_from);
    }
}

EventEmitter (实际 emit 事件的合约)

pragma solidity 0.8.4;

contract EventEmitter{
    event TokensFrom(address From);

    function emitTokenFrom(address _from) public {
        emit TokensFrom(_from);
    }
}

一个快速的工作流程:

调用者 approve OtherContract 花费 5 个代币。

调用者使用 OtherContract 合约中的 setEmitterAddress 函数设置 EventEmitter 合约地址。

调用者调用 OtherContract 合约的 takeTokenAndCallOtherFunction 函数,该函数然后调用 emitTokensTaken 函数。

emitTokensTaken 函数调用位于 EventEmitter 合约中的 emitTokenFrom 函数。

emitTokenFrom 函数只是 emit TokensFrom 事件。


安装和设置 hardhat-tracer :

使用 npm i hardhat-tracer 安装

将其添加到 hardhat.config.js:require(“hardhat-tracer”);

让我们为智能合约编写一个简单的测试用例:

在下面的测试中,你可以看到四个函数调用:

it("test",async () => {
    // Approve OtherContract.
    console.log("------------------------------------ approve tokens ------------------------------------");
    await token1Instance.approve(OtherContractInstance.address,ethers.utils.parseEther("5"));
    console.log("----------------------------------------------------------------------------------------\n");

    console.log("----------------------------------- set emitter addr -----------------------------------");
    await OtherContractInstance.setEmitterAddress(EventEmitterInstance.address);
    console.log("----------------------------------------------------------------------------------------\n");

    console.log("-------------------------- call takeTokenAndCallOtherFunction --------------------------")
    await OtherContractInstance.takeTokenAndCallOtherFunction(token1Instance.address);
    console.log("----------------------------------------------------------------------------------------\n")

    console.log("---------------------- failed takeTokenAndCallOtherFunction call -----------------------")
    await expect(OtherContractInstance.takeTokenAndCallOtherFunction(token1Instance.address)).to.be.revertedWith("ERC20: insufficient allowance");
    console.log("----------------------------------------------------------------------------------------\n")
});
  1. 第一次调用 approve OtherContract 地址花费 5 个代币
  2. 第二次调用使用 setEmitterAddress 在 OtherContract 中设置 EventEmitter 合约地址。
  3. 第三次调用使用我们已授予 approve 的代币地址调用 takeTokenAndCallOtherFunction
  4. 第四次是失败的 takeTokenAndCallOtherFunction 调用,其中没有剩余为 OtherContract 花费的 approve 代币。

让我们用 hardhat-tracer 跟踪这些调用:

现在只需使用 test 命令 + trace 标志运行测试文件:例如:> npx hardhat test test/token-trace.js --trace

你可以在终端中看到交易跟踪:

Test screenshot 1

hardhat-tracer 允许你设置地址名称标签以识别地址。你可以在测试调用之前或创建合约实例之后添加它们来设置地址,如下所示:

hre.tracer.nameTags[OtherContractInstance.address] = "OtherContract";
hre.tracer.nameTags[EventEmitterInstance.address] = "EventEmitter";
hre.tracer.nameTags[token1Instance.address] = "TKN1";
hre.tracer.nameTags[alice.address] = "Alice";

添加这些标签后,你可以在跟踪中看到地址的直接名称/标签:

traces when address tags added

在上面的屏幕截图中,你可以看到使用构造函数参数创建合约的跟踪,以及所有权从零地址转移到 msg.sender。

回到第一个测试用例调用,即 approve 代币的调用:

第二次调用是 OtherContract.setEmitterAddress() 来设置 emitter 地址。

第三次是一个有趣的调用,这个调用基本上调用了另一个函数,然后再次调用 Emitter 合约中的函数。下面的屏幕截图显示了 OtherContract.takeTokenAndCallOtherFunction() 函数的跟踪,该函数调用 transferFrom() 函数,该函数然后将 approve 设置为零(approve 为 5 个代币)并将 5 个代币转账到 OtherContract 地址,你可以看到 ApprovalTransfer 的事件跟踪。

之后,它调用 emitTokensTaken() 函数,然后调用 emitter 合约中的 emitTokenFrom() 函数,该函数最终 emit TokensFrom 事件。你可以在下面的屏幕截图中看到这一点:

最后,最后一个是失败的 takeTokenAndCallOtherFunction() 调用,它将 revert。在这里,你可以看到 takeTokenAndCallOtherFunction() 正在调用 transferFrom(),由于 0 allowance(takeTokenAndCallOtherFunction 函数至少需要 5 个代币的 allowance)而失败。然后,你可以看到 allowance 不足的 revert 消息。


上面的例子非常小,并且没有复杂的外部合约调用,但是在浏览像大型项目 fork 这样的复杂代码,甚至是包含许多调用的 fork 网络时,跟踪对于理解代码级别上发生的事情非常有用。

  • 原文链接: calibersec.com/tracing-s...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
calibersec
calibersec
江湖只有他的大名,没有他的介绍。