Hardhat测试中向合约转奖励代币及claimPrize报错解决
问题排查与修复方案
你遇到的Address: call to non-contract报错不是你猜测的合约无代币余额导致,是合约代码、测试代码存在多个基础错误叠加导致,所有问题点和修复方式如下:
核心报错原因
- 测试代码中硬编码的奖励代币地址
0xAddr.....是无效占位地址,Hardhat本地测试链上该地址未部署任何合约,调用该地址的代币转账方法时直接触发非合约地址调用报错。 - 除此之外你的代码还存在多个会导致测试/运行失败的问题:
- Solidity合约未导入IERC20、SafeERC20依赖,
safeTransfer方法无定义 - 合约内
Winner结构体定义的奖励字段名为reward,但claim逻辑中错误引用了不存在的tokenAmount字段 - 构造函数中引用了未定义的
_addr_1/_addr_2变量,合约无法正常编译 addWinner方法未加权限校验,任意地址都可私自添加获奖地址- 合约未实现
owner()方法,测试用例中校验owner的逻辑会直接失败 - 测试代码添加获奖者时传入的是字符串
"winner1.address",不是实际的账户地址变量 - 测试中非获奖者校验逻辑调用了不存在的
claimReward()方法,合约实际方法名为claimPrize() - 测试环境未部署测试用ERC20代币合约,也未给奖励合约转入任何测试代币
- Solidity合约未导入IERC20、SafeERC20依赖,
修复后的合约代码
先修正Solidity合约的所有语法、逻辑问题:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.5; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract LeagueWinners { using SafeERC20 for IERC20; struct Winner { bool exists; bool claimed; uint256 reward; } mapping(address=>Winner) public winners; mapping (address => bool) private AuthAccounts; IERC20 private rewardTokenAddr; address public owner; modifier onlyAuthAccounts() { require(AuthAccounts[msg.sender], "Auth: caller is not the authorized"); _; } constructor (address _rewardTokenAddr) { rewardTokenAddr = IERC20(_rewardTokenAddr); owner = msg.sender; AuthAccounts[msg.sender] = true; } function addWinner(address _address, uint256 _amount ) public onlyAuthAccounts { Winner storage winner = winners[_address]; winner.exists = true; winner.reward = _amount; } function claimPrize() public { Winner storage winner = winners[msg.sender]; require(winner.exists, "Not a winner"); require(!winner.claimed, "Winner already claimed"); winner.claimed = true; rewardTokenAddr.safeTransfer(msg.sender, winner.reward); } }
测试场景下给合约转入奖励代币的方法
本地测试不要用主网/测试网的真实代币地址,先在项目中新增一个测试用模拟ERC20合约,部署后再通过标准ERC20转账方法给奖励合约充值测试代币。
- 先在
contracts/mocks/目录下新建MockERC20.sol作为测试用代币:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockERC20 is ERC20 { constructor() ERC20("Test Reward Token", "TRD") {} // 公开mint方法方便测试生成测试币 function mint(address to, uint256 amount) external { _mint(to, amount); } }
- 修复后的完整测试代码如下:
const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("LeagueWinners", function () { let leagueWinners, rewardToken, owner, winner1, winner2, nonwinner; const SINGLE_WINNER_REWARD = ethers.utils.parseEther("50"); // 单个获奖者奖励50代币 const TOTAL_REWARD_POOL = ethers.utils.parseEther("1000"); // 总奖励池1000代币 before(async () => { [owner, winner1, winner2, nonwinner] = await ethers.getSigners(); // 部署测试用ERC20代币 const RewardTokenFactory = await ethers.getContractFactory("MockERC20"); rewardToken = await RewardTokenFactory.deploy(); await rewardToken.deployed(); // 部署奖励合约,传入测试代币地址 const LeagueWinnersFactory = await ethers.getContractFactory("LeagueWinners"); leagueWinners = await LeagueWinnersFactory.deploy(rewardToken.address); await leagueWinners.deployed(); // 给奖励合约充值测试代币:先给owner mint足够测试币,再转总奖励额度到奖励合约地址 await rewardToken.mint(owner.address, TOTAL_REWARD_POOL); await rewardToken.connect(owner).transfer(leagueWinners.address, TOTAL_REWARD_POOL); }); it("Should set deployer as contract owner", async function () { expect(await leagueWinners.owner()).to.equal(owner.address); }); it("Should add winner with correct reward amount", async function () { await leagueWinners.connect(owner).addWinner(winner1.address, SINGLE_WINNER_REWARD); const winnerInfo = await leagueWinners.winners(winner1.address); expect(winnerInfo.exists).to.equal(true); expect(winnerInfo.reward).to.equal(SINGLE_WINNER_REWARD); }); it("Should revert when non-winner tries to claim", async function () { await expect( leagueWinners.connect(nonwinner).claimPrize() ).to.be.revertedWith("Not a winner"); }); it("Should allow winner to claim prize successfully", async function () { const beforeClaimBalance = await rewardToken.balanceOf(winner1.address); await leagueWinners.connect(winner1).claimPrize(); const winnerInfo = await leagueWinners.winners(winner1.address); const afterClaimBalance = await rewardToken.balanceOf(winner1.address); expect(winnerInfo.claimed).to.equal(true); expect(afterClaimBalance.sub(beforeClaimBalance)).to.equal(SINGLE_WINNER_REWARD); }); it("Should revert when winner tries to claim twice", async function () { await expect( leagueWinners.connect(winner1).claimPrize() ).to.be.revertedWith("Winner already claimed"); }); });
内容的提问来源于stack exchange,提问作者Gourav Naik
相关产品推荐
相关产品推荐

