You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试布尔值?NftMarketplace提款回退测试报错求助

NFT Marketplace提款失败回退测试问题

我在完成NFT Marketplace单元测试的最后一个用例时遇到问题:想测试提款操作失败时合约触发回退,但找不到有效的布尔值测试方案。原本思路是让非NFT所有者的player3尝试提款触发转账失败,但测试运行时出现大量错误(仅贴前3行):

reverts if transfer fails:
     Error: invalid signer or provider (argument="signerOrProvider", value={"interface":{"fragments":[{"name":null,"type":"constructor","inputs":[],"payable":false,"stateMutability":"nonpayable","gas":null,"_isFragment":true},{"type":"error","name":"NftMarketplace__AlreadyListed","inputs":[{"name":"nftAddress","type":"address","indexed":null,"components":null,"arrayLength":null,"arrayChildren":null,"baseType":"address","_isParamType":true},{"name":"tokenId","type":"uint256","indexed":null,"components":null,"arrayLength":null,"arrayChildren":null,"baseType":"uint256","_isParamType":true}] .................................................(error is way too long)

合约代码片段

function withdrawProceeds() external {
        uint256 proceeds = s_proceeds[msg.sender];
        if (proceeds <= 0) {
            revert NftMarketplace__NoProceeds();
        }
        s_proceeds[msg.sender] = 0; // 先重置状态,防止重入攻击
        (bool success, ) = payable(msg.sender).call{value: proceeds}("");
        if (!success) {
            revert NftMarketplace__TransferFailed();
        }
    }

我的测试代码

it("reverts if transfer fails", async () => {
                player1 = accounts[2]
                await nftMarketplace.listItem(basicNft.address, TOKEN_ID, PRICE) // 挂售NFT
                const playerConnectedNftMarketplace = nftMarketplace.connect(player) // 连接玩家1
                await playerConnectedNftMarketplace.buyItem(basicNft.address, TOKEN_ID, { // 玩家1购买NFT,让部署者获得收益,避免触发NftMarketplace__NoProceeds()
                    value: PRICE,
                })
                const proceeds = await nftMarketplace.getProceeds(deployer.address) // 此时部署者已有收益
                const player1NftMarketplace = nftMarketplace.connect(player1)
                nftMarketplace = await ethers.getContract("NftMarketplace", player1NftMarketplace)
                await expect(
                    player1NftMarketplace.withdrawProceeds()
                ).to.be.revertedWithCustomError(nftMarketplace, "NftMarketplace__TransferFailed")
            })

问题分析与修复

  1. 测试逻辑偏差:让无收益的用户提款,只会触发NftMarketplace__NoProceeds()回退,而非转账失败的NftMarketplace__TransferFailed()。要触发转账失败,必须模拟有收益但无法接收ETH的账户,普通外部账户默认能接收ETH,需用合约模拟。
  2. 代码语法错误:nftMarketplace = await ethers.getContract("NftMarketplace", player1NftMarketplace)是错误用法,getContract的第二个参数应为签名者(signer),而非已连接的合约实例,这直接导致了invalid signer or provider错误。

修复后的测试方案

第一步:部署拒绝接收ETH的测试合约

// 测试用合约,拒绝接收ETH
contract RevertingReceiver {
    receive() external payable {
        revert("Transfer rejected");
    }
}

第二步:修改测试用例

it("reverts if transfer fails", async () => {
    // 部署拒绝接收ETH的合约
    const revertingReceiver = await ethers.deployContract("RevertingReceiver");
    
    // 将NFT转移给拒绝接收合约,让它作为卖家挂售
    await basicNft.connect(deployer).transferFrom(deployer.address, revertingReceiver.address, TOKEN_ID);
    await nftMarketplace.connect(revertingReceiver).listItem(basicNft.address, TOKEN_ID, PRICE);
    
    // 玩家购买该NFT,让拒绝接收合约获得收益
    await nftMarketplace.connect(player).buyItem(basicNft.address, TOKEN_ID, { value: PRICE });
    
    // 验证拒绝接收合约有收益
    const receiverProceeds = await nftMarketplace.getProceeds(revertingReceiver.address);
    expect(receiverProceeds).to.equal(PRICE);
    
    // 拒绝接收合约尝试提款,此时转账会失败,触发目标回退
    await expect(
        nftMarketplace.connect(revertingReceiver).withdrawProceeds()
    ).to.be.revertedWithCustomError(nftMarketplace, "NftMarketplace__TransferFailed");
});

关键说明

  • 触发NftMarketplace__TransferFailed必须满足两个条件:用户有收益,且向该用户转账ETH失败。重写receive()函数并回退的合约是模拟该场景的标准方式。
  • 切换合约签名者直接使用contract.connect(signer)即可,无需调用ethers.getContract重复获取合约实例。

内容的提问来源于stack exchange,提问作者kihiuFrank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 07:25:33