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

自定义权限modifier导致Solidity合约测试用例运行失败

问题原因

报错来自三处代码逻辑问题:

  1. 合约构造函数里硬编码的_addr_1、_addr_2是无效占位符,没有对应Hardhat测试环境里随机生成的测试账户地址,测试部署时授权映射AuthAccounts根本没有把你调用方法用的owner地址加入白名单,触发权限校验拦截。
  2. 你贴出的addWinner函数漏加了自定义的onlyAuthAccounts修饰器——如果真的没加修饰器根本不会触发权限报错,说明你实际运行的代码加了修饰器但贴代码时遗漏,要注意权限修饰器必须显式标注在目标函数上才会生效。
  3. 你没有继承OpenZeppelin的Ownable合约,也没有自行实现owner()方法,第一个测试用例调用leagueWiners.owner()本身就会因为方法不存在直接报错,和权限问题无关。
修复方案

第一步:调整合约代码

把硬编码授权地址的逻辑改成构造函数传参,方便部署(含测试部署)时灵活配置授权地址,同时给addWinner补上权限修饰器。修正后的合约代码如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.5;

contract LeagueWinners {

    struct Winner {
        bool exists;
        bool claimed;
        uint256 reward;
    }

    mapping(address=>Winner) public winners;
    mapping (address => bool) private AuthAccounts;

    modifier onlyAuthAccounts() {
        require(AuthAccounts[msg.sender], "Auth: caller is not the authorized");
        _;
    }

    // 构造函数接收两个授权地址作为入参,不再硬编码固定地址
    constructor (address _auth1, address _auth2) {
        AuthAccounts[_auth1] = true;
        AuthAccounts[_auth2] = true;
    }

    // 补上权限修饰器
    function addWinner(address _address, uint256 _amount ) public onlyAuthAccounts {
        Winner storage winner = winners[_address];
        winner.exists = true;
        winner.reward = _amount;
    }
}

第二步:调整测试代码

Hardhat启动测试时会默认生成10个临时测试签名账户,部署合约时把要用于测试的授权地址传入构造函数,删掉合约未实现的owner()相关校验逻辑,补充必要的结果断言。修正后的测试代码如下:

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("LeagueWinners", function () {
  let leagueWinners;
  let auth1, auth2, nonAuth, winnerAddr;
  
  before(async () => {
    // 从测试环境取测试账户:前两个做授权账户,第三个做非授权测试账户,第四个做获奖者地址
    [auth1, auth2, nonAuth, winnerAddr] = await ethers.getSigners();
    const LeagueWinners = await ethers.getContractFactory("LeagueWinners");
    // 部署时传入两个授权地址
    leagueWinners = await LeagueWinners.deploy(auth1.address, auth2.address);
    await leagueWinners.deployed();
  });

  it("should block non-authorized account from adding winner", async function () {
    // 验证非授权账户调用会被拦截
    await expect(
      leagueWinners.connect(nonAuth).addWinner(winnerAddr.address, ethers.utils.parseEther("50"))
    ).to.be.revertedWith("Auth: caller is not the authorized");
  });

  it("should allow authorized account to add winner", async function () {
    // 用授权账户调用添加获奖者方法
    const addTx = await leagueWinners
      .connect(auth1)
      .addWinner(
        winnerAddr.address,
        ethers.utils.parseEther("50") // 用官方工具做单位转换,避免手写长串零数错位数
      );
    await addTx.wait();
    // 验证获奖者信息正确上链
    const winnerInfo = await leagueWinners.winners(winnerAddr.address);
    expect(winnerInfo.exists).to.equal(true);
    expect(winnerInfo.reward).to.equal(ethers.utils.parseEther("50"));
  });
});
注意事项
  • 硬编码固定地址的写法仅适合正式网部署时写死多签/管理员地址,测试环境的账户地址是每次启动临时生成的,硬编码的固定地址永远匹配不上测试账户,必然触发权限报错。
  • 测试逻辑要和合约实现对齐,合约没实现的方法不要强行调用,否则会报方法不存在的错误。
  • 链上金额默认单位是wei,尽量用ethers.utils.parseEther这类官方工具做单位转换,不要手动写一长串数字,避免位数错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 12:01:12