如何测试布尔值?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") })
问题分析与修复
- 测试逻辑偏差:让无收益的用户提款,只会触发
NftMarketplace__NoProceeds()回退,而非转账失败的NftMarketplace__TransferFailed()。要触发转账失败,必须模拟有收益但无法接收ETH的账户,普通外部账户默认能接收ETH,需用合约模拟。 - 代码语法错误:
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
相关产品推荐
相关产品推荐

