
fhEVM 端到端测试套件实战指南SDK 源切换、统一用户解密、算子边界与 Smoke 冒烟运行【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm导读本文以test-suite/e2e为核心完整讲解 fhEVM 全栈框架中端到端E2E测试工程的设计与用法覆盖四大主题fhevm/sdk依赖源本地构建 vs npm registry的切换机制、基于统一 EIP-712 信封eip712-unified-user-decrypt-v1的用户解密测试套件、算术/移位/旋转/类型转换算子的边界用例以及面向 Sepolia/Mainnet/Devnet 的生产级冒烟运行器smoke runner。读完本文你将掌握如何在真实网络上配置环境变量、控制签名者故障切换与 gas 策略并理解 E2E 测试中哪些拒绝发生在 relayer 层、哪些只发生在 KMS Connector 层的断言模型。一、测试套件全景目录结构与角色定位test-suite/e2e是 fhEVM 仓库根目录 README.md中基于 Hardhat 的端到端测试工程其 package.json 声明了fhevm/e2e-suite这一 npm 包名依赖hardhat^2.23.0、ethers^6.15.0、openzeppelin/contracts^5.3.0、safe-global/safe-contracts等。整个工程围绕四类测试资产组织资产类别关键路径职责合约源码contracts/冒烟合约SmokeTestInput、TestInput算子套件FHEVMOperatorEdgeCaseTestSuite、FHEVMManualTestSuiteERC-1271 钱包ERC1271ApproveHashWallet/ERC1271OwnerWallet/ERC1271RejectWallet等测试用例test/解密、算子、桥接、多链、暂停协议、共识等待等 100 个 TS 用例运行脚本scripts/install-sdk.shSDK 安装切换、smoke-inputflow.ts冒烟运行器、smoke-reporting.ts失败分类与上报、gen_handles.ts运行入口run-tests.sh、hardhat.config.ts测试过滤/网络选择与 Hardhat 网络定义hardhat.config.ts 定义了丰富的网络拓扑staging默认、zwsDev、sepolia、mainnet、polygon、polygonAmoy以及本地/容器化协处理器链localCoprocessor、composeCoprocessorL1/L2等统一使用 HD 钱包120 个账户由MNEMONIC派生默认派生路径m/44/60/0/0并为 staging/zwsDev 场景默认回落到http://localhost:8545。二、fhevm/sdk依赖源切换本地构建与 registry 版本2.1 为什么需要单独安装 SDKtest-suite/e2e不在package.json中声明fhevm/sdk依赖——它由 scripts/install-sdk.sh 在安装时原地注入既可以从本地源码构建打包也可以从 npm registry 安装固定版本全程不触碰package.json/package-lock.json。这种设计让测试套件既能验证仓库内sdk/js-sdk的最新改动又能对照某个已发布版本做回归。2.2 使用本地源码构建默认路径在 Docker 构建与默认场景下SDK 从仓库内的sdk/js-sdk源码构建并打包安装cd test-suite/e2e npm run sdk:localnpm run sdk:local实际执行./scripts/install-sdk.sh local。从 install-sdk.sh 的源码可以看到其关键行为调用sdk/js-sdk/test/scripts/rebuild_sdk_and_pack.sh --build-profile$BUILD_PROFILE完成构建与打包产物为sdk/js-sdk/test/manual-pack/fhevm-sdk-*.tgz在test-suite/e2e目录执行npm install --no-save --no-workspaces --legacy-peer-deps安装该 tarball脚本对--no-workspaces与--legacy-peer-deps有专门注释说明前者避免 npm 把fhevm/sdk提升hoist到仓库根node_modules导致 ethers 无法解析后者因为本次安装是从零解析依赖树不同于根目录信任 lockfile 的npm ci而safe-global/safe-contracts的 peer 依赖ethers5.4.0与项目其余部分的ethers^6.15.0冲突需要--legacy-peer-deps放行安装完成后脚本会恢复fhevm/solidity的 workspace 链接--no-workspaces安装会把fhevm/solidity从 workspace link指向library-solidity替换成 registry 的真实副本导致 E2E 悄悄编译到可能过期的 Solidity 库因此脚本删除后重新软链回$ROOT_DIR/library-solidity。迭代提示本地安装是一次性打包one-off pack并非实时软链接——每次修改sdk/js-sdk源码后都必须重新运行npm run sdk:local。2.3 加速开发SDK_BUILD_PROFILEdev默认构建 profile 为prod见 install-sdk.sh 中BUILD_PROFILE${SDK_BUILD_PROFILE:-prod}。在开发迭代期间可以先设置SDK_BUILD_PROFILEdev再执行npm run sdk:local获得更快、未压缩unminified的构建产物便于断点调试cd test-suite/e2e SDK_BUILD_PROFILEdev npm run sdk:local2.4 安装特定 registry 版本npm run sdk:registry -- version对应install-sdk.sh registry version。registry 模式必须显式传版本号没有默认版本可回退因为 package.json 故意不固定fhevm/sdkcd test-suite/e2e npm run sdk:registry -- 0.13.2两种模式共用同一套npm install --no-save --no-workspaces --legacy-peer-deps安装策略并同样在结尾恢复fhevm/solidityworkspace 链接。install-sdk.sh 的文件头注释明确了这套机制复用了sdk/js-sdk/test/browser-next下refresh-sdk.sh的npm install --no-save技术。三、统一用户解密Unified User-Decryption套件3.1 三个测试目录与覆盖范围README 定义的统一解密 E2E 覆盖分为三个目录围绕 ERC-1271 智能账户签名验证与统一的 EIP-712 用户解密请求展开test/erc1271UserDecryption/ —— 智能账户签名模式与全部 ERC-1271 拒绝路径。目录内含safe.ts真实 Safe 多签签名拼接、erc1271SdkClientGap.ts配套合约见 contracts/erc1271/test/unifiedUserDecryption/ ——allowedContracts模式、有效期窗口validity window、直接委托混合批次mixed directdelegated batches、extraData版本矩阵test/decryptionSignatureInvalidation/ —— 链上签名失效及其端到端影响包括多签轮换multisig-rotation场景。3.2 为什么需要自研客户端这些套件直接向 relayer 的/v3/user-decrypt端点 POST 统一的eip712-unified-user-decrypt-v1信封客户端封装在 test/sdk/unified/unifiedUserDecrypt.ts。该文件头注释解释得很清楚公共fhevm/sdk在 protocol 0.14 时也构造同一信封但总是以连接的 signer 身份签名且不暴露这些套件必须控制的字段——与 ECDSA 签名者不同的userAddress智能账户、空签名approveHash 流程、自定义extraData版本、自定义startTimestamp、以及刻意构造的畸形形状用于负向用例。3.3 信封结构与 EIP-712 类型UNIFIED_ATTESTATION_TYPE eip712-unified-user-decrypt-v1是 relayer/v3/user-decrypt端点唯一接受的 attestation 类型。EIP-712 类型列表UNIFIED_USER_DECRYPT_TYPES定义如下字段顺序具有权威性——它决定 EIP-712 类型哈希必须与 Solidity 结构体、KMS Connector、relayer、js-sdk 的kmsUserDecryptEip712V2Types完全一致注释明确警告不要重排UserDecryptRequestVerification: [ { name: userAddress, type: address }, { name: publicKey, type: bytes }, { name: allowedContracts,type: address[] }, { name: startTimestamp, type: uint256 }, { name: durationSeconds, type: uint256 }, { name: extraData, type: bytes }, ]EIP-712 domain 的name/version取自 GatewayDecryption合约DOMAIN_NAME Decryption、DOMAIN_VERSION 1。默认extraData为0x00版本字节 0无上下文 ID。值得注意的一个实现细节是 chainIdFromHandleEIP-712 domain 的chainId不是从配置读取而是从密文 handle 本身推导——FHEVM handle 以大端字节序在第 [22, 30) 字节编码创建时的链 ID与 relayer 签名预检读取的切片一致从而保证 digest 与 relayer 重算结果匹配。3.4 四种签名模式SignMode统一解密请求支持四种签名生成方式模式语义适用场景eoasigner直接签名且signer.address必须等于userAddressEOA 快速路径签名前会显式校验二者一致否则报错提示改用erc1271常规 EOA 用户解密erc1271由ownerSigner某位 owner 的私钥签名userAddress为智能钱包地址KMS/relayer 通过钱包的isValidSignature验证智能账户Smart Accountempty不提供签名0x对应 Safe 的approveHash/signedMessages流程Safe 预先批准raw直接透传预先构造好的签名 blob——包括 Safe 多签拼接见test/erc1271UserDecryption/safe.ts的buildSafeMultisigSignature以及故意构造的畸形 blob负向用例多签与畸形输入3.5 断言模型拒绝发生在哪一层该 helper 的注释文档化了完整的断言模型这是理解套件写法的关键。POST /v3/user-decrypt的响应语义为202携带jobId 接受其他状态码 拒绝。拒绝可分为三个层次Relayer 同步预检签名验证共享的verify_signature先做ecrecover失败再回退 ERC-1271。确定性的坏签名得到POST 400 invalid_signature合法签名得到POST 202 queued。isSignatureRejection通过匹配{field: signature, issue: Signature is invalid}将签名语义拒绝与信封签名字段校验失败畸形 hex区分开——后者共享相同 label/field 但 issue 文本不同。Relayer 每任务 host-ACL 检查逐 handle 的所有权/委托 ACL 失败会以终态failed呈现error.label not_allowed_on_host_acl——用expectRelayerAclRejection断言钉死失败原因防止测试在意外失败上蒙混过关。仅 KMS Connector 强制执行的检查allowedContracts语义、签名失效、extraData上下文/epoch 校验。这类拒绝不会向 relayer 返回可见响应——任务在整个观察窗口内保持queued。用expectStuckAtKms断言状态恰好为pending从而把拒绝模式钉死在KMS 层拒绝任何意外失败坏签名→400、ACL 失败→failed都会使该断言失败。KMS-Connector 拒绝永不触达 relayer任务会在 relayer 默认的user_decrypt_timeout30 分钟才被收割因此套件的观察窗口选择为短于任何 relayer 侧超时、长于真实成功所需时间窗口结束时仍pending即为真实拒绝。此外还有expectGatewayRevert断言 Gateway 链上模拟失败导致任务终态failed、requestUnifiedUserDecrypt构造签名提交可选轮询的端到端便捷入口其中对202 却无 jobId的情况快速失败以避免负向用例空转通过。正向用例凡是 SDK 可表达的EOA 或委托 handle会额外通过公共 SDK 解密同一 handle 并断言已知明文而钱包所有 handleuserAddress为合约无法走公共 SDK 解密其正向用例只断言succeeded。3.6 运行方式可通过 fhevm-cli profiles 运行erc1271-user-decryption、unified-user-decryption、decryption-signature-invalidation均属于standard测试车道详见 test-suite/fhevm/README.md也可直接用 Hardhat 过滤运行npx hardhat test --grep describe title --network staging如果 relayer 前面挂了认证网关需要设置ZAMA_FHEVM_API_KEY以x-api-key头发送与 js-sdkApiKeyHeader认证模式使用的默认 header 一致。四、算子边界用例套件Operator Edge Cases4.1 覆盖范围test/fhevmOperations/下的operatorEdgeCases*.ts系列覆盖算术、移位、旋转和类型转换算子的极限情况过度移位overshift移位量 位宽除法/取模边界与DivisionByZero()revert上溢/下溢回绕over/underflow wrapping窄化类型转换截断narrowing-cast truncation。4.2 语义参考与 tfhe-rs 版本联动期望值定义在 test/fhevmOperations/shiftSemantics.ts它以OVERSHIFT_RETURNS_ZERO false为开关实现参考语义。从源码可见当OVERSHIFT_RETURNS_ZERO false当前值时采用传统语义过大的移位量被截断为amount % bits后再移位expectedShl/expectedShr旋转算子expectedRotl/expectedRotr恒按amount % bits计算注释明确tfhe-rs 1.7.0 时过度移位返回 0 而非截断移位量因此升级引擎版本时必须同步把OVERSHIFT_RETURNS_ZERO翻转为true二者必须同一改动提交否则边界用例会与引擎行为不一致。4.3 运行命令./fhevm-cli test operators --grep edge cases --verbosefhevm-cli位于 test-suite/fhevm其test heavy车道即算子覆盖车道operators测试默认自动启用--parallel并行。五、Smoke 冒烟运行器inputFlow5.1 定位scripts/smoke-inputflow.ts 以 Hardhat 为运行时、配合加固的事务处理逻辑运行单条链上冒烟流程加密输入encrypt uint647→ 链上调用add42ToInput64→ 用户解密 公开解密断言结果均为49。流程细节可从源码确认先通过createInstance()初始化 fhEVM 实例并加密值7n以instance.encryptUint64产出 handle 与 inputProof再调用合约的add42ToInput64(handle, inputProof)最后依次执行userDecryptSingleHandle与publicDecrypt([handle])分别断言49n与{handle: 49n}。5.2 前置条件PrereqsSepolia/Mainnet大部分配置由 SDK 自动填充SepoliaConfig/MainnetConfig只需提供RPC_URL或SEPOLIA_ETH_RPC_URL/MAINNET_ETH_RPC_URLMNEMONICZAMA_FHEVM_API_KEY仅 mainnet 需要Devnet使用预配置的 .env.devnet全部地址已内置DOTENV_CONFIG_PATH./.env.devnet npx hardhat run --network devnet scripts/smoke-inputflow.ts其他网络staging、自定义需手工设置全部变量参考 .env.example包含 Gateway 的CHAIN_ID_GATEWAY/DECRYPTION_ADDRESS、Host 的ACL_CONTRACT_ADDRESS/KMS_VERIFIER_CONTRACT_ADDRESS/FHEVM_EXECUTOR_CONTRACT_ADDRESS等。网络相关的 RPC URL 规则与 hardhat.config.ts 的网络定义对应staging / zwsDevRPC_URL默认回落 localhost:8545sepoliaSEPOLIA_ETH_RPC_URL回落到RPC_URLmainnetMAINNET_ETH_RPC_URL回落到RPC_URLPod 部署只需设置RPC_URL对所有网络通用设置TEST_INPUT_CONTRACT_ADDRESS可复用已部署的合约需要SMOKE_DEPLOY_CONTRACT0。Hardhat 默认从test-suite/e2e/.env加载环境变量可用DOTENV_CONFIG_PATH覆盖也可用 Hardhat vars 存储密钥例如npx hardhat vars set SEPOLIA_ETH_RPC_URL会交互式提示输入。5.3 签名者配置与故障切换冒烟运行器使用由MNEMONIC派生的 HD 钱包签名者默认使用索引0,1,2做自动故障切换某个签名者事务卡住时切换到另一个。实现细节见 smoke-inputflow.ts启动时打印所有可用签名者的latest/pendingnonce 与余额余额低于0.005 ETHLOW_BALANCE_THRESHOLD时告警选号器优先选干净pending latest且余额充足按当前 base fee × 约 100 万 gas 估算minUsableBalance的签名者无干净签名者时若 backlog 不超过SMOKE_MAX_BACKLOG默认 3且余额足以支付取消费用则先对主签名者执行 backlog 取消每个 pending nonce 发送一笔 0 值、gasLimit21000的自转账事务发送采用sendWithRetries在 base fee 基础上按feeBump逐次指数抬价并区分 ethers v6 的CALL_EXCEPTION已上链但 revert终态错误不重试、TIMEOUT超时抬价重试、TRANSACTION_REPLACED仅repriced视为成功cancelled/replaced视为非本进程事务并告警重试最终还会扫描所有已发送 hash 兜底查找迟到上链的回执成功后清理对仍有 backlog 的签名者尝试取消 pending 事务但清理步骤失败不判定冒烟失败。用 Foundrycast从助记词派生签名者地址以便打款cast wallet address --mnemonic your mnemonic here --mnemonic-index 0 cast wallet address --mnemonic your mnemonic here --mnemonic-index 1 cast wallet address --mnemonic your mnemonic here --mnemonic-index 25.4 冒烟专用旋钮默认值环境变量默认值说明SMOKE_SIGNER_INDICES0,1,2用于故障切换的签名者索引逗号分隔SMOKE_TX_TIMEOUT_SECS48单笔事务等待超时对应 12 秒/块 × 4 块SMOKE_TX_MAX_RETRIES2事务最大重试次数SMOKE_FEE_BUMP1.125^4每次重试的费率抬升乘数SMOKE_MAX_FEE_GWEI未设置无上限maxFeePerGas超过此上限则快速失败SMOKE_MAX_PRIORITY_FEE_GWEI未设置无上限maxPriorityFeePerGas超过此上限则快速失败SMOKE_MAX_BACKLOG3允许自动取消的 pending 事务数上限SMOKE_CANCEL_BACKLOG1置0关闭 pending 事务自动取消SMOKE_DEPLOY_CONTRACT1置0则通过TEST_INPUT_CONTRACT_ADDRESS挂接已有合约SMOKE_RUN_TESTS1置0仅部署合约不运行测试SMOKE_DECRYPT_TIMEOUT_SECS300解密操作超时BETTERSTACK_HEARTBEAT_URL未设置成功时 ping BetterStack失败时以错误码上报gas 上限同样在源码中有据可查部署与add42ToInput64调用前都会先estimateGas再乘以120%作为 gasLimit 缓冲SMOKE_GAS_ESTIMATE 1_000_000约 100 万 gas 覆盖部署调用。失败分类与上报由 scripts/smoke-reporting.ts 完成它会输出SMOKE_FAILED class...、SMOKE_FAILED_SUMMARY、SMOKE_FAILED_RELAYER_META从异常中提取 relayer URL/操作/jobId/响应体等结构化日志并把timeout/decrypt_timeout/no_clean_signer/tx_all_attempts_failed等归类为告警提示hint。5.5 运行cd test-suite/e2e npx hardhat run --network zwsDev scripts/smoke-inputflow.ts npx hardhat run --network sepolia scripts/smoke-inputflow.ts npx hardhat run --network mainnet scripts/smoke-inputflow.ts六、快速运行任意测试run-tests.sh 与 fhevm-cli除冒烟脚本外run-tests.sh 是通用测试入口参数包括-g/--grep测试过滤文本默认test user input uint64、-n/--network默认staging、-v/--verbose、--parallel、--no-hardhat-compile跳过编译因为test默认会通过 Hardhat 重新编译合约./run-tests.sh ./run-tests.sh -g test user input uint64 ./run-tests.sh -n staging -g my test ./run-tests.sh my test # 位置参数同样生效 ./run-tests.sh --parallel --no-hardhat-compile -n sepolia -g decryption更上层的组织方式是 test-suite/fhevm 的fhevm-cliprofilestest light为轻量冒烟车道input-prooferc20test standard为默认 CI 车道含 DB 回滚与漂移test multi-chain-isolation为多链覆盖车道test heavy为算子车道详见 test-suite/fhevm/README.md。七、可继续深入的相关仓库资源test-suite/e2e/README.md本测试工程的官方说明test-suite/e2e/hardhat.config.ts全部网络、链 ID、HD 账户与 gas 报告配置test-suite/e2e/scripts/smoke-inputflow.ts冒烟运行器完整实现签名者选择、fee bump、backlog 取消test-suite/e2e/test/sdk/unified/unifiedUserDecrypt.ts统一解密客户端与断言辅助函数test-suite/e2e/test/fhevmOperations/shiftSemantics.ts移位/旋转语义参考与 tfhe-rs 版本联动开关test-suite/fhevm/README.mdfhevm-cli 车道与 profiles 定义sdk/js-sdk被install-sdk.sh local模式构建打包的公共 SDK 源码relayer统一用户解密/v3/user-decrypt端点的服务端实现对应relayer/src/http下的 handler 与签名预检逻辑。八、总结test-suite/e2e是 fhEVM 面向真实链环境的体检中心通过install-sdk.sh灵活地在本地 SDK 构建与 registry 发布版本之间切换依赖源用eip712-unified-user-decrypt-v1信封直连 relayer/v3/user-decrypt把签名验证relayer 同步预检、host-ACLrelayer 每任务检查与allowedContracts/签名失效KMS Connector 静默拒绝三层拒绝路径用三种不同的断言方式区分开来算子边界用例通过shiftSemantics.ts与 tfhe-rs 版本严格联动冒烟运行器则以多签名者故障切换、费率抬升、backlog 取消与 BetterStack 心跳上报为 Sepolia/Mainnet/Devnet 提供了可在无人值守 CI 中长期运行的链上健康检查。【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考