Web3调试技巧

383 阅读6分钟

前言

在开发 Web3 的过程中总会莫名其妙产生各种各样的问题,有时候一个问题会困扰自己很长时间,而 Web3 不像 Web2 一样能够找到一个热心同事手把手教你怎么解决,几乎全部要靠自己。就是因为这一点,我决定写这一篇文章帮助大家来调试 dApp。

那么本篇文章所用之案例就是上一篇实战:免费手把手教你Web3入门实战,没看过的 jy 可以去把上一篇实战完整的实现一下,实现过程中产生的问题可以到本篇文章中查询

console.log调试

可以引入 hardhat 提供的 console 函数,通过 console.log 打印日志,这也是非常有效的一种方式,在前端调用合约失败之时,我们只需要在合约的函数中打好日志,然后再次调用,查看有哪些值与我们期望的不同就能排查出问题了。

比如说打印 isActive 看看这个 NFT 是否是激活状态:

import "hardhat/console.sol";
...
/**
 *
 * @dev 从 s_listings 中获取上架的 NFT
 */
function getListsArray(
    address _nftContract
) public view returns (Listing[] memory) {
    Listing[] memory listings = new Listing[](s_tokenIds.length);
    for (uint256 i = 0; i < s_tokenIds.length; i++) {
        Listing memory item = s_listings[_nftContract][s_tokenIds[i]];
        console.log("isActive",item.isActive);
        listings[i] = item;
    }
    return listings;
}
...

看起来这个 console.log 和前端的 console.log真的是一模一样。但是真的是一样的吗?在 getListsArray 中我经常会遇到一个问题,那就是返回的值中 isActive 和 s_listings 中的 isActive 不同步,导致 BUG,这个时候我就想打印一下 s_listings 这个映射看一看,究竟是哪里的问题。

/**
 *
 * @dev 从 s_listings 中获取上架的 NFT
 */
function getListsArray(
    address _nftContract
) public view returns (Listing[] memory) {
    Listing[] memory listings = new Listing[](s_tokenIds.length);
    console.log("s_listings",s_listings);
    for (uint256 i = 0; i < s_tokenIds.length; i++) {
        Listing memory item = s_listings[_nftContract][s_tokenIds[i]];
        listings[i] = item;
    }
    return listings;
}

结果直接给我报错了,编译不通过。

image.png

总结一下:console.log可以方便地打印日志,但是它的缺点也很明显:

1. 很多类型的值都不支持直接打印出来,例如映射、结构体、数组等,只能打印一些“简单类型”的值,很鸡肋

2. 每次遇到问题都要修改合约,重新部署一下然后看效果

前期我还使用过这种方式调试,但是现在我已经抛弃了。

抛出异常

巧用异常抛出的参数进行故障排查,在定义异常时都可以指定参数,除了必要的 message 之外,还可以指定一些关键值,这对于故障排查十分有效。在 NFTMarket 合约中定义了这么多异常,基本上报了异常之后就可以知道是哪里出了问题。

image.png

前端读取合约调试

前提:需要把一些关键的变量设置为 public

比如说开发过程中遇到了一个这样的问题:从 getListingsArray 返回的数组中 NFT 的状态和我预期不同,我需要调试

于是我将 s_listings 可见性设置为 public,然后在 Nextjs 中读取 s_listings:

export function useListings(tokenId) {
  const { data, error } = useReadContract({
    ...NFTMarketParams,
    functionName: "s_listings",
    args: [NftTokenContractAddress, tokenId],
  });

  console.log("error", error);

  return data;
}

这里可以打印出一个结构体,但是它是以数组的形式展示的,如下: 截屏2024-11-17 14.35.28.png 通过对比发现 getListingsArray 传入的 nftContractAddress 与 listNft 中传入的地址不同,所以导致取值有问题,修改之后就没有问题了。

最后还有一种方式:利用 Remix 根据交易 hash 进行 debug,但是具体过程太麻烦了,我也没试过,等以后试过了再补充吧,感兴趣的可以自己试一试。最后把上一节中遇到的一些问题列在下面,方便查阅:

问题合集(便于搜索)

  1. hardhat 部署时产生问题了,导致调用的方法名无法被识别,直接展示一个 <unrecognized-selector> image.png

  2. mint方法报错了,从下图可以看出 mint 方法报错了,但是不知道是哪里的问题,这个时候就去看 hardhat 控制台日志 截屏2024-11-15 22.09.31.png 截屏2024-11-15 22.10.50.png 这是一个常见的报错:需要重置 MetaMask 数据,具体操作如下: 截屏2024-11-15 22.13.03.png 截屏2024-11-15 22.13.25.png 截屏2024-11-15 22.13.41.png 完成之后大功告成,问题解决!

  3. 获取到了值,但是对象中属性值都是初始化值,这是由于 Solidity 中的映射(mapping),在取值时如果映射右边是一个结构体,即使取不到也不会返回空值,而是直接初始化一个结构体出来。出现这种情况一般就是 mapping 的 key 值传错了,比如传的合约的地址错误等等

  4. mapping 无法直接返回,Solidity 确实不支持 mapping 直接返回,但是在 mapping 的 view function 中是可以返回某个 key 对应的属性值的,犹豫不决的时候可以看下 abi,abi 中定义了所有 Solidity 与前端页面交互的对象,基本上是我们的“接口文档”了。

截屏2024-11-15 22.25.35.png

  1. 修改 mapping 中的对象属性值时建议这样连着写:s_listings[_nftContract][_tokenId].isActive = false;,而 Listing memory item = s_listings[_nftContract][_tokenId]; item.isActive=false; 这样写可能不生效