链上协议测试别只停在单元层
2026/9/18 16:20:35 网站建设 项目流程

链上协议测试别只停在单元层

DeFi 协议不能只靠本地单元测试判断是否可上线。单函数断言和余额变化能发现许多回归,却很难覆盖跨合约调用、外部价格源和复杂状态顺序。

漏洞成因并不只有一种,数学实现、权限、集成配置和经济激励都可能出问题。Ethereum 的外部调用与 Solana 的 CPI、账户约束又有不同的失败方式。单元测试是基础,后面仍应补集成测试、属性测试和接近真实环境的演练;这些测试也不能替代发布审批和持续监控。


1. 异构生态下的分层测试

Ethereum 的 EVM 串行单线程状态机与 Solana 的 Sealevel 多线程账户模型在并发与存储上有着本质差异。构建可靠的 DeFi 协议,必须建立跨生态的分层测试体系。

1.1 单元测试(L1)的局限性

单元测试旨在校验独立函数的逻辑正确性。但在 Solana 中,程序(Program)本身是没有状态的,所有状态存储在外部 Account 中;在 Ethereum 中,跨合约调用的外部状态变更也无法被单合约单元测试完整捕获。

1.2 跨协议集成测试(L2)的核心关注点

  • Ethereum 生态:重点验证在闪电贷注入数千万美元流动性时,协议所依赖的 Uniswap V3 或 Chainlink 预言机是否会被瞬间扭曲,导致非预期清算。
  • Solana 生态:重点验证 CPI(Cross-Program Invocation)调用时,传入的 AccountInfo 数组是否被恶意替换,是否存在 Program ID 伪造与 Account Owner 越权。

1.3 端到端攻击仿真(L3)

在接近主网的本地网络(如 Anvil 或 Solana Surfnet)中,模拟并发交易抢跑(Front-running)、夹心攻击(Sandwich Attack)以及网络拥堵导致清算失败的极端场景。


2. Solana 生态 Anchor + Bankrun 高阶测试代码

在 Solana 开发中,常规的solana-program-test运行缓慢。使用solana-bankrun能够在 Rust/TypeScript 端实现毫秒级的账户快照与 Anchor 程序 CPI 攻击模拟。

下面的代码展示如何模拟 Solana 上的账户伪造与账户所有权校验漏洞测试:

// tests/solana-defi-security.test.ts import { describe, it, expect, beforeAll } from "vitest"; import { startAnchor } from "solana-bankrun"; import { PublicKey, Keypair, SystemProgram } from "@solana/web3.js"; import { Program, AnchorProvider, BN } from "@coral-xyz/anchor"; import { IDL, SecureVault } from "../target/types/secure_vault"; const PROGRAM_ID = new PublicKey("Vault11111111111111111111111111111111111111"); describe("Solana DeFi 跨程序调用与账户伪造防范测试", () => { let context: any; let provider: AnchorProvider; let program: Program<SecureVault>; const adminKeypair = Keypair.generate(); const attackerKeypair = Keypair.generate(); const fakeTokenAccount = Keypair.generate(); beforeAll(async () => { // 使用 Bankrun 高速启动轻量级本地链上下文,载入编译好的 Anchor 程序 context = await startAnchor( "./", [{ name: "secure_vault", programId: PROGRAM_ID }], [ { address: adminKeypair.publicKey, info: { lamports: 10_000_000_000, data: Buffer.alloc(0), owner: SystemProgram.programId, executable: false }, }, { address: attackerKeypair.publicKey, info: { lamports: 5_000_000_000, data: Buffer.alloc(0), owner: SystemProgram.programId, executable: false }, }, ] ); provider = new AnchorProvider(context.banksClient as any, adminKeypair as any, {}); program = new Program<SecureVault>(IDL, PROGRAM_ID, provider); }); it("必须拒绝 Owner 非官方 Token Program 的伪造账户 (Account Privilege Escalation)", async () => { // 构造一个恶意伪造的 Token Account(Owner 为攻击者程序,而非 SPL Token Program) const fakeAccountInfo = { address: fakeTokenAccount.publicKey, info: { lamports: 1_000_000, data: Buffer.from("fake_data_payload"), owner: attackerKeypair.publicKey, // ⚠️ 恶意归属权 executable: false, }, }; // 手动注入伪造账户状态到 Bankrun 上下文 context.setAccount(fakeTokenAccount.publicKey, fakeAccountInfo.info); try { // 尝试调用 Vault 的 提现/清算 逻辑 await program.methods .withdrawVault(new BN(1000)) .accounts({ vaultState: PublicKey.default, tokenAccount: fakeTokenAccount.publicKey, // 传入伪造账户 authority: attackerKeypair.publicKey, tokenProgram: SystemProgram.programId, }) .signers([attackerKeypair]) .rpc(); expect.fail("系统应当抛出账户 Owner 不匹配异常,但执行成功了!"); } catch (err: any) { // 断言 Anchor 框架层成功拦截了 Account Owner Mismatch 错误 console.log("[Bankrun Pass] 成功拦截伪造账户攻击:", err.message); expect(err.toString()).toContain("ConstraintOwner"); } }); });

3. Ethereum 生态 EVM 闪电贷与预言机操纵测试

在 EVM 侧,集成测试的核心在于使用 Hardhat 或 Foundry 构造主网 Fork 场景,模拟闪电贷在单个区块内砸盘并试图套利的攻击模式。

// test/evm-flashloan-oracle.test.ts import { expect } from "chai"; import { ethers, network } from "hardhat"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; describe("EVM DeFi 闪电贷价格防操纵集成测试", function () { let deployer: SignerWithAddress; let attacker: SignerWithAddress; let lendingPool: any; let mockOracle: any; const UNISWAP_V3_PAIR_WETH_USDC = "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"; before(async function () { // 重置 Hardhat 到指定的 Ethereum 主网高度 await network.provider.request({ method: "hardhat_reset", params: [ { forking: { jsonRpcUrl: process.env.MAINNET_RPC_URL || "", blockNumber: 17000000, }, }, ], }); [deployer, attacker] = await ethers.getSigners(); // 部署具备 TWAP (时间加权平均价) 防护的预言机合约 const OracleFactory = await ethers.getContractFactory("SecureTwapOracle"); mockOracle = await OracleFactory.deploy(UNISWAP_V3_PAIR_WETH_USDC, 300); // 300秒 TWAP 窗口 const PoolFactory = await ethers.getContractFactory("DeFiLendingPool"); lendingPool = await PoolFactory.deploy(await mockOracle.getAddress()); }); it("当遭遇瞬时闪电贷砸盘时,TWAP 预言机应保持稳定,防止非预期的异常清算", async function () { // 1. 模拟闪电贷借出 10,000 WETH 并砸入 Uniswap 池子 const flashLoanAmount = ethers.parseEther("10000"); console.log("[Test Log] 触发模拟闪电贷瞬间抛售..."); // 2. 在同一 Block 内查询 Lending Pool 评估的抵押物价值 const collateralPriceBefore = await mockOracle.getAssetPrice(); // 模拟恶意巨额 Swap await executeSpotPriceManipulation(UNISWAP_V3_PAIR_WETH_USDC, flashLoanAmount); const collateralPriceAfter = await mockOracle.getAssetPrice(); // 断言 TWAP 预言机价格变动绝对不能超过 1% 安全阀值(即便现货现价已被扭曲 30%) const priceDiff = collateralPriceBefore > collateralPriceAfter ? collateralPriceBefore - collateralPriceAfter : collateralPriceAfter - collateralPriceBefore; const percentageChange = (priceDiff * 100n) / collateralPriceBefore; console.log(`[Oracle Deviation] 瞬时现价剧烈波动,但 TWAP 偏差仅为: ${percentageChange}%`); expect(percentageChange).to.be.lt(1n); // 偏差必须小于 1% }); }); async function executeSpotPriceManipulation(pairAddress: string, amount: bigint) { // 模拟现货池深度扭曲的底层辅助函数 }

4. DeFi 工程交付的质量守护流程

超越单一的单元测试,构建具备抗打击能力的 DeFi 协议,必须在团队落地以下规范:

第一,建立多维度的测试矩阵。L1 单元测试负责基本的数学逻辑与状态转移;L2 集成测试负责跨合约 CPI 账户归属与主网 Fork 预言机抗性;L3 端到端仿真负责在高并发与 MEV 抢跑场景下进行压力测试。

第二,针对 Solana 生态强制执行 Rust Anchor 约束检查。对于每一个传入程序的AccountInfo,必须显式校验其owner是否为预期的 Program ID,严防账户替换与未授权修改。

第三,在 EVM 生态中杜绝使用 Spot Price(现货即时价格)作为清算依据。所有涉及借贷、清算的逻辑,必须在 Mainnet Fork 测试集中加入闪电贷注入测试,验证 TWAP 或去中心化 Chainlink 预言机的防操纵韧性。

只有将测试场景拉入真实的对抗环境,才能在险象环生的 Web3 链上工程中确保用户资产万无一失。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询