本地测试合约
本地测试提供快速迭代与确定性可复现性。但本地 EVM 模拟器无法完全建模 OXN 的机密性行为。本页解释本地能测什么、不能测什么、如何组织你的测试金字塔。
本地网络能测的
标准 Solidity 合约逻辑——所有在以太坊上无修改能跑的东西——都能在本地正常测试:
- 合约状态转移
- 访问控制(用
msg.sender) - 事件 emit(topic 与 data 结构)
- Revert 与错误条件
- 多合约交互
- Gas 使用模式(近似)
本地网络不能测的
以下行为依赖 OXN 运行时,不只是 EVM:
- 加密 calldata 处理——本地网络看到 calldata 都是明文,任何关于"这次调用是否加密到达?"的断言在本地没有意义。
- 机密存储——本地存储公开可读;"外部无法读这个槽"的测试在任何本地网络上都会假绿。
- 签名查询——签名查询路径需要 OXN 网关。本地
eth_call默认无认证。 - 机密预编译——
Sapphire.randomBytes、Sapphire.encrypt等在标准 Hardhat network 上不存在。 - PUSH0 拒绝——本地网络(Hardhat、Anvil)默认实现 PUSH0。用
shanghai编译的合约在本地跑得好好的,只有到真 OXN 才失败。
推荐的测试金字塔
| 层级 | 位置 | 覆盖范围 |
|---|---|---|
| 单元测试 | Hardhat / Foundry 本地 | 业务逻辑、数学、访问控制、revert 条件。 |
| 集成测试 | OXN 测试网 | 机密性行为、加密 calldata、签名查询。 |
| 端到端测试 | OXN 测试网 + 真实钱包 | 钱包交互、水龙头流程、面向用户的路径。 |
每个 dApp 都应该在真实 OXN 上跑至少一部分集成测试。 不要假设 Hardhat 绿了就代表生产绿。
Hardhat 本地测试
标准 Hardhat 测试开箱即用:
test/MyToken.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("MyToken", function () {
it("mints initial supply to deployer", async function () {
const [deployer] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy(ethers.parseUnits("1000", 18));
expect(await token.balanceOf(deployer.address))
.to.equal(ethers.parseUnits("1000", 18));
});
});
npx hardhat test 运行。Hardhat 起一个内存网络、部署、断言。
Foundry 本地测试
test/MyToken.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken token;
address deployer = address(this);
function setUp() public {
token = new MyToken(1_000_000 ether);
}
function testInitialSupply() public {
assertEq(token.balanceOf(deployer), 1_000_000 ether);
}
}
forge test 运行。Foundry 内建的 Test 合约提供断言辅助方法与 cheatcode。
Fixture 与快照
用 fixture 避免重复部署,测试更快:
Hardhat 的 loadFixture
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
async function deployTokenFixture() {
const [deployer, other] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy(ethers.parseUnits("1000", 18));
return { token, deployer, other };
}
describe("MyToken transfers", function () {
it("transfers tokens", async function () {
const { token, deployer, other } = await loadFixture(deployTokenFixture);
await token.transfer(other.address, ethers.parseUnits("100", 18));
expect(await token.balanceOf(other.address))
.to.equal(ethers.parseUnits("100", 18));
});
});
Fixture 在 setUp 后快照网络状态、每次测试前恢复,全套测试快 10-100 倍。
时间与区块操纵
Hardhat 与 Foundry 都暴露 cheatcode 用于在本地测试里把时间推进或设置特定区块条件。对时间锁定合约很有用:
Hardhat time cheatcodes
const { time } = require("@nomicfoundation/hardhat-network-helpers");
// 前进 1 小时
await time.increase(3600);
// 跳到指定时间戳
await time.increaseTo(1700000000);
Foundry time cheatcodes
vm.warp(block.timestamp + 3600); // 前进 1 小时
vm.roll(block.number + 100); // 推进 100 个区块
这些 cheatcode 只在本地。真实 OXN 不接受客户端的时间操纵。