前言
在开发 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;
}
结果直接给我报错了,编译不通过。
总结一下:console.log可以方便地打印日志,但是它的缺点也很明显:
1. 很多类型的值都不支持直接打印出来,例如映射、结构体、数组等,只能打印一些“简单类型”的值,很鸡肋
2. 每次遇到问题都要修改合约,重新部署一下然后看效果
前期我还使用过这种方式调试,但是现在我已经抛弃了。
抛出异常
巧用异常抛出的参数进行故障排查,在定义异常时都可以指定参数,除了必要的 message 之外,还可以指定一些关键值,这对于故障排查十分有效。在 NFTMarket 合约中定义了这么多异常,基本上报了异常之后就可以知道是哪里出了问题。
前端读取合约调试
前提:需要把一些关键的变量设置为 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;
}
这里可以打印出一个结构体,但是它是以数组的形式展示的,如下:
通过对比发现 getListingsArray 传入的 nftContractAddress 与 listNft 中传入的地址不同,所以导致取值有问题,修改之后就没有问题了。
最后还有一种方式:利用 Remix 根据交易 hash 进行 debug,但是具体过程太麻烦了,我也没试过,等以后试过了再补充吧,感兴趣的可以自己试一试。最后把上一节中遇到的一些问题列在下面,方便查阅:
问题合集(便于搜索)
-
hardhat 部署时产生问题了,导致调用的方法名无法被识别,直接展示一个
<unrecognized-selector> -
mint方法报错了,从下图可以看出 mint 方法报错了,但是不知道是哪里的问题,这个时候就去看 hardhat 控制台日志
这是一个常见的报错:需要重置 MetaMask 数据,具体操作如下:
完成之后大功告成,问题解决!
-
获取到了值,但是对象中属性值都是初始化值,这是由于 Solidity 中的映射(mapping),在取值时如果映射右边是一个结构体,即使取不到也不会返回空值,而是直接初始化一个结构体出来。出现这种情况一般就是 mapping 的 key 值传错了,比如传的合约的地址错误等等
-
mapping 无法直接返回,Solidity 确实不支持 mapping 直接返回,但是在 mapping 的 view function 中是可以返回某个 key 对应的属性值的,犹豫不决的时候可以看下 abi,abi 中定义了所有 Solidity 与前端页面交互的对象,基本上是我们的“接口文档”了。
- 修改 mapping 中的对象属性值时建议这样连着写:
s_listings[_nftContract][_tokenId].isActive = false;,而Listing memory item = s_listings[_nftContract][_tokenId]; item.isActive=false;这样写可能不生效