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

使用Hardhat、Chai/Mocha测试Solidity库合约的方法及报错解决

Solidity 场景下 Hardhat + Chai/Mocha 测试库合约最佳实践

报错根因说明

你遇到的Types containing (nested) mappings can only be parameters or return variables of internal or library functions是Solidity原生语法限制:所有包含mapping(含嵌套mapping)的类型,仅允许作为internal函数、库函数的入参或返回值,无法作为public/external对外函数的参数或返回值——这类类型不具备标准ABI编码能力,无法和链下交互。
直接把库改写为普通合约测试的思路本身不成立:库中很多面向内部调用设计的、接收带mapping的storage类型的逻辑,改成普通合约后会被识别为对外函数,直接触发编译报错。

推荐测试方案

全程不需要修改原有业务库的代码,避免破坏生产逻辑:

  • 编写专用测试包装合约(最通用,覆盖所有库场景)
    单独创建仅用于测试的包装合约,在合约内部声明库对应的带mapping的状态变量,通过using 库名 for 类型语法引入库方法,把需要测试的库逻辑包装成不带mapping类型参数/返回值的external方法,对外只暴露需要交互的入参、需要断言的具体字段值。
    示例代码:
    // 原有业务库,保持生产代码原样不做任何修改
    library AssetLib {
        struct AssetStore {
            mapping(address => uint256) userHoldings;
            uint256 totalSupply;
        }
    
        function mint(AssetStore storage self, address to, uint256 amount) internal {
            self.userHoldings[to] += amount;
            self.totalSupply += amount;
        }
    }
    
    // 测试专用包装合约,单独放在test目录下
    contract AssetLibTestHelper {
        using AssetLib for AssetLib.AssetStore;
        AssetLib.AssetStore internal _store; // 带mapping的状态变量存在合约内部,不对外暴露
    
        // 包装库的mint方法,入参只传链下可编码的普通类型
        function mint(address to, uint256 amount) external {
            _store.mint(to, amount);
        }
    
        // 对外暴露需要断言的具体字段,不要返回整个AssetStore结构体
        function getHolding(address user) external view returns (uint256) {
            return _store.userHoldings[user];
        }
    
        function getTotalSupply() external view returns (uint256) {
            return _store.totalSupply;
        }
    }
    
    测试时直接部署这个AssetLibTestHelper合约,调用它暴露的方法即可,完全不会触发mapping类型的编译错误,也能100%覆盖原库的逻辑。
  • 直接链接库测试(仅适用于无storage操作的纯函数库)
    如果你的库全是不涉及storage操作的纯计算逻辑,没有任何带mapping的类型传参,可以直接在Hardhat脚本中部署库合约,再把库地址链接到测试调用合约上,直接调用库的public方法即可。
  • Chai断言注意事项
    不要尝试在JS/TS测试代码中接收、断言带mapping的结构体,只需要通过包装合约暴露的视图方法拿到具体的基础类型值,再用Chai的equal、deepEqual等方法做断言即可。如果要测事件触发,直接在包装合约里透传原库触发的事件,在测试脚本里监听即可。

避坑要点

  • 不要为了适配测试修改原库的函数可见性,比如把internal方法改成public,既会破坏库的调用语义,还会给生产合约留下不必要的公开接口风险。
  • 不要尝试在链下测试脚本中直接传入带mapping的类型参数,EVM没有针对这类类型的ABI编码标准,调用必然失败。
  • 不需要额外配置即可让Hardhat的solidity-coverage插件识别包装合约的调用,正确统计原库的测试覆盖率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 04:15:30