news 2026/9/12 13:26:34

使用 Foundry 编写 FHEVM 智能合约测试:forge-fhevm 完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 Foundry 编写 FHEVM 智能合约测试:forge-fhevm 完整实战指南

使用 Foundry 编写 FHEVM 智能合约测试:forge-fhevm 完整实战指南

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

本篇技术指南以 fh/fhevm 仓库的 Foundry 测试文档为核心,系统讲解如何基于 forge-fhevm(Foundry 原生的 FHEVM 测试库)编写全同态加密智能合约的单元测试。你将掌握FhevmTest基类继承、encrypt*输入加密助手、三种解密模式(decrypt/publicDecrypt/userDecrypt)以及完整的加密 → 执行 → 解密测试闭环,并可直接用于本仓库中的FHECounter等加密合约项目。

为什么在 Foundry 中测试 FHEVM 合约

FHEVM 合约与普通 Solidity 合约的关键区别在于:合约内部存储与操作的是密文句柄(handle),而非明文值;调用方必须为每个加密输入附带 EIP-712 格式的输入证明(input proof),由链上的InputVerifier验证后才能执行FHE.fromExternal。这些逻辑无法用传统 Mock 测试覆盖,必须在测试环境中真实部署 FHEVM 主网合约(host contracts)。

forge-fhevm解决这一问题的思路是:把 FHEVM 的 host contracts——FHEVMExecutorACLInputVerifierKMSVerifier——直接部署进 Foundry 的测试 EVM 中,让被测合约运行与生产环境完全相同的链上代码路径:输入验证、ACL 权限强制、句柄生命周期全部按主网行为执行。唯一的差异是 FHE 协处理器计算被模拟:明文值在本地跟踪,因此测试里可以直接对解密结果做assertEq断言(参见 Foundry 指南 README)。

同时,测试环境与主网唯一的区别在于使用了模拟私钥(mock input signer 与 mock KMS signer),从而保证测试中 EIP-712 证明的确定性生成。

继承 FhevmTest 基类

每个 FHEVM 测试合约都必须继承FhevmTest。在setUp()中调用super.setUp()会将 FHEVM host contracts 部署到它们的规范确定性地址(canonical deterministic addresses)上:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.27; import {FhevmTest} from "forge-fhevm/FhevmTest.sol"; import {FHE} from "@fhevm/solidity/lib/FHE.sol"; import "encrypted-types/EncryptedTypes.sol"; contract MyTest is FhevmTest { MyContract myContract; function setUp() public override { super.setUp(); // deploy FHEVM host contracts myContract = new MyContract(); } }

⚠️注意:被测合约必须继承一个 Zama 配置(例如ZamaEthereumConfig),这样FHE.*调用才会路由到setUp()部署的 FHEVM host contracts 上。

关于这一点可以从仓库源码中得到印证:ZamaConfig.sol 中的ZamaEthereumConfig在构造时调用FHE.setCoprocessor(ZamaConfig.getEthereumCoprocessorConfig()),将 ACL、协处理器、KMSVerifier 的地址注入 FHE 库。该库按block.chainid路由配置:Ethereum 主网(1)、Polygon(137)、Sepolia(11155111)、Polygon Amoy(80002)以及本地 Hardhat/Anvil(31337),本地链的规范地址硬编码在_getLocalConfig()中。这正是super.setUp()能按确定性地址部署、且FHE.fromExternal能正确路由的前提。

setUp() 注入的状态变量

根据 forge-fhevm API 参考,super.setUp()会为测试合约注入以下基础设施:

变量类型作用
_executorFHEVMExecutor处理 FHE 运算并发出驱动明文跟踪的事件
_aclACL按句柄的访问控制(transient 与 persistent)
_inputVerifierInputVerifier验证 EIP-712 输入证明(1 个 mock 签名者)
_kmsVerifierKMSVerifier验证 EIP-712 解密证明(1 个 mock 签名者)
MOCK_INPUT_SIGNERaddress模拟输入签名者地址
MOCK_KMS_SIGNERaddress模拟 KMS 签名者地址

💡 这些 mock 签名者密钥是 Zama 特有值,硬编码在forge-fhevm/src/FhevmTest.sol中,并非Foundry 标准的测试私钥。它们的存在仅仅是为了让测试中的 EIP-712 证明保持确定性。

加密输入:encrypt* 助手

任何调用FHE.fromExternal的合约,都需要调用方提供一个(handle, proof)对。FhevmTest提供了一组encrypt*助手来构建这个二元组。

两步/三步重载

每个助手都有两个重载:

  • 两参数重载:使用address(this)作为隐式用户(即测试合约自身):
(externalEuint64 amount, bytes memory proof) = encryptUint64(100, address(myContract));
  • 三参数重载:将证明绑定到指定用户:
address alice = address(0xA11CE); (externalEuint64 amount, bytes memory proof) = encryptUint64(100, alice, address(myContract));

调用被测合约

拿到(handle, proof)后,用 Foundry 的vm.prank模拟用户身份发起调用:

vm.prank(alice); myContract.deposit(amount, proof);

支持的加密助手一览

函数值类型返回句柄
encryptBoolboolexternalEbool
encryptUint8uint8externalEuint8
encryptUint16uint16externalEuint16
encryptUint32uint32externalEuint32
encryptUint64uint64externalEuint64
encryptUint128uint128externalEuint128
encryptUint256uint256externalEuint256
encryptAddressaddressexternalEaddress

💡 每次调用encrypt*都会递增一个内部 nonce,因此对同一个值加密两次会产生不同的句柄——这也保证了测试不会因句柄复用而出现意外碰撞。

仓库中的加密合约示例可以印证这套流程的实际用法:在 fhe-counter.md 示例 的FHECounter.increment中,合约通过FHE.fromExternal(inputEuint32, inputProof)验证并导入外部密文,随后执行FHE.add,并通过FHE.allowThis(_count)FHE.allow(_count, msg.sender)为句柄授予 ACL 权限——这正是下文userDecrypt模式所依赖的持久化 ACL 权限来源。

解密结果:三种解密模式

forge-fhevm提供三种解密模式,分别对应生产环境的不同解密流程。选择哪一种,取决于被测合约的调用模式。

decrypt(handle)—— 底层查找

直接返回句柄对应的明文,不做任何 ACL 或证明检查。适合单元断言场景,例如检查合约内部状态:

euint64 balance = myContract.balanceHandle(alice); assertEq(decrypt(balance), 100);

decrypt()为每种加密类型提供了类型化重载,返回对应的 Solidity 原始类型:

bool a = decrypt(myEbool); uint8 b = decrypt(myEuint8); uint64 c = decrypt(myEuint64); address d = decrypt(myEaddress);

底层签名是decrypt(bytes32 handle) returns (uint256),各类型重载只是将其转换为匹配的 Solidity 原始类型(booluint8……uint256address)。

publicDecrypt(handles)—— KMS 签名的公开解密

当被测合约在链上通过FHE.checkSignatures()验证解密证明时使用。它返回明文数组与一个 KMS 签名证明:

bytes32[] memory handles = new bytes32[](1); handles[0] = euint64.unwrap(balance); (uint256[] memory cleartexts, bytes memory proof) = publicDecrypt(handles); FHE.checkSignatures(handles, abi.encode(cleartexts), proof); assertEq(cleartexts[0], 100);

⚠️ 如果被测合约未对该句柄调用过FHE.makePubliclyDecryptable()publicDecrypt()将回滚并抛出HandleNotAllowedForPublicDecryption错误。

userDecrypt(handle, user, contract, signature)—— 面向用户的解密流程

完整模拟生产环境的用户解密:包含持久化 ACL 检查与 EIP-712 签名验证:

uint256 constant ALICE_PK = 0xA11CE; address alice = vm.addr(ALICE_PK); // (通过业务逻辑 mint 或 transfer 将 ACL 授予 alice) bytes memory sig = signUserDecrypt(ALICE_PK, address(myContract)); uint256 cleartext = userDecrypt( euint64.unwrap(myContract.balanceHandle(alice)), alice, address(myContract), sig ); assertEq(cleartext, 100);

signUserDecrypt是 EIP-712 用户解密签名助手,其完整签名族包括:

// 单合约版本 function signUserDecrypt(uint256 userPk, address contractAddress) view returns (bytes memory signature); // 多合约 + 有效期版本 function signUserDecrypt( uint256 userPk, address[] memory contractAddresses, uint256 startTimestamp, uint256 durationDays ) view returns (bytes memory signature);

userDecrypt流程中可能触发的错误及原因:

错误原因
UserAddressEqualsContractAddressuserAddress == contractAddress
UserNotAuthorizedForDecrypt用户缺少持久化ACL 权限
ContractNotAuthorizedForDecrypt合约缺少持久化ACL 权限
InvalidUserDecryptSignature签名无法还原出userAddress

💡 ACL 权限由被测合约在其业务逻辑中授予——例如在 token 的mint中调用FHE.allow(balance, owner)。测试中无需手动授权,这正是 FHECounter.sol 同目录的docs/examples/fhe-counter.mdincrement末尾FHE.allow(_count, msg.sender)的作用:业务逻辑完成后,调用方天然具备对结果的解密权限。

附加的证明助手

FhevmTest还提供两个底层证明构造助手,适用于回调风格(callback-style)的流程:

// KMS 签名的解密证明(无 ACL 检查) function buildDecryptionProof(bytes32[] memory handles, bytes memory abiEncodedCleartexts) view returns (bytes memory proof); function buildDecryptionProof(bytes32 handle, bytes memory abiEncodedCleartext) view returns (bytes memory proof);

完整计数器测试示例

下面是一个完整的、开箱即用的 FHE 计数器测试(原文档引自fhevm-foundry-template/test/FHECounter.t.sol,仓库内与之对应的合约实现在 docs/examples/fhe-counter.md 的FHECounter.sol中):

contract FHECounterTest is FhevmTest { FHECounter counter; uint256 internal constant ALICE_PK = 0xA11CE; address alice; function setUp() public override { super.setUp(); counter = new FHECounter(); alice = vm.addr(ALICE_PK); } function test_incrementTheCounterByOne() public { (externalEuint32 encOne, bytes memory proof) = encryptUint32(1, alice, address(counter)); vm.prank(alice); counter.increment(encOne, proof); bytes memory sig = signUserDecrypt(ALICE_PK, address(counter)); uint256 clear = userDecrypt(euint32.unwrap(counter.getCount()), alice, address(counter), sig); assertEq(clear, 1); } }

这个测试完整覆盖了 FHEVM 合约的典型生命周期:

  1. 加密encryptUint32(1, alice, address(counter))为用户 alice 生成指向计数器合约的加密输入;
  2. 执行vm.prank(alice)模拟 alice 调用counter.increment(encOne, proof),链上完成FHE.fromExternal输入验证与FHE.add密文运算;
  3. 解密signUserDecrypt生成 EIP-712 签名,userDecrypt走完整的 ACL + 签名验证流程取回明文;
  4. 断言assertEq(clear, 1)验证结果。

运行测试

在项目根目录执行:

forge test -vvv

只运行单个测试:

forge test --match-test test_incrementTheCounterByOne -vvv # single test

-vvv级别会输出完整的事件与 trace,便于排查FHE.fromExternal验证失败或 ACL 报错等链上细节。

项目环境配置要点

为了让上述测试代码可以运行,需要正确的 Foundry 工程配置(详见 Setup Foundry):

foundry.toml关键项forge-fhevm面向 Cancun EVM 与较新的 Solidity 编译器,evm_version = "cancun"是必要配置;依赖通过 Soldeer 管理,安装命令为forge soldeer install

remappings.txt依赖映射(版本占位符按 Soldeer 实际写入的目录替换):

@fhevm/host-contracts/=dependencies/forge-fhevm-<rev>/src/fhevm-host/ @fhevm/solidity/=dependencies/@fhevm-solidity-<version>/ encrypted-types/=dependencies/@encrypted-types-<version>/ forge-fhevm/=dependencies/forge-fhevm-<rev>/src/ forge-std/=dependencies/forge-std-<version>/src

验证安装:可以写一个最小测试确认 host contracts 部署成功:

// test/Setup.t.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.27; import {FhevmTest} from "forge-fhevm/FhevmTest.sol"; contract SetupTest is FhevmTest { function test_setupDeploys() public view { // setUp() deploys all FHEVM host contracts at deterministic addresses assertTrue(address(_executor) != address(0)); assertTrue(address(_acl) != address(0)); } }
forge test --match-test test_setupDeploys -vv

测试与部署的衔接

测试通过后,合约可以沿两条路径部署(详见 Deploy FHEVM contracts with Foundry):

  • Sepolia 测试网:FHEVM 协议栈已部署在规范地址上,合约通过继承的ZamaEthereumConfig自动获取 ACL、协处理器与 KMSVerifier 地址,只需用forge script --rpc-url <sepolia> --broadcast --verify广播部署脚本;
  • 本地 Anvil 节点:先用 forge-fhevm 的deploy-local.sh将 host contracts 以setCode/setStorageAt方式物化到本地节点的规范地址,再部署自己的合约。

这与测试环境的行为保持一致:测试与本地部署共享同一套ZamaConfig._getLocalConfig()规范地址(chainId 31337),因此"测试通过"对本地部署行为具有直接参考价值。

小结

forge-fhevm让 FHEVM 合约测试回归到与普通合约相同的开发体验:FhevmTest基类 +super.setUp()完成全部链上基础设施搭建,encrypt*系列助手解决密文输入的构造与证明生成,三种解密模式分别对应用户解密、公开解密与底层断言三种生产场景。配合本仓库的 FHE 库源码、示例合约 以及 forge-fhevm API 参考,你可以在几分钟内为任意 FHEVM 合约搭建出覆盖"加密 → 执行 → 解密 → 断言"完整闭环的测试套件。

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 13:25:53

2026论文写作工具测评:8款AI辅助工具对比分析

1. 项目概述&#xff1a;论文写作工具的现状与需求 2026届毕业生即将面临毕业论文写作的高峰期&#xff0c;而科研工作者也常年需要应对繁重的学术写作任务。在这个背景下&#xff0c;各类智能写作辅助工具如雨后春笋般涌现&#xff0c;它们承诺能够"一键生成"论文内…

作者头像 李华
网站建设 2026/9/12 13:24:57

MFC ActiveX曲线控件在工控上位机中的应用与集成

简介&#xff1a;这是一套基于MFC ActiveX技术开发的工控图形控件集&#xff0c;包含曲线、折线、柱状图三种绘制控件&#xff0c;面向Windows桌面应用开发者&#xff0c;主要解决工业监控系统中传感器数据与历史数据的实时可视化、报表展示等问题。资源包共158个文件&#xff…

作者头像 李华
网站建设 2026/9/12 13:24:34

AI编程助手搞定Google登录与Stripe支付:提示词与实操全解析

做SaaS或者独立项目的时候&#xff0c;Google登录和Stripe支付几乎是“老三样”里的老二老三——排第一的是部署上线。这两块功能本身不难&#xff0c;但繁琐&#xff1a;回调地址、环境变量、Webhook签名、测试卡号&#xff0c;任何一个环节对不上都能卡你半小时。我见过不少人…

作者头像 李华
网站建设 2026/9/12 13:23:18

AI原生平台四大生死线:可解释性、可干预性、可演进性深度评测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 13:20:04

2026年大模型技术对比:ChatGPT与Gemini核心差异与应用

1. 2026年大模型技术格局前瞻当我在2023年第一次使用GPT-4时&#xff0c;那种震撼感至今记忆犹新——它不仅能流畅对话&#xff0c;还能解决复杂的编程问题。三年后的今天&#xff0c;大模型技术已经演进到令人惊叹的程度。2026年的大模型领域&#xff0c;ChatGPT和Gemini两大技…

作者头像 李华