Skip to main content

formal-verification

Use when applying formal verification to Solidity contracts using Certora CVL or Halmos symbolic testing. Covers specification writing, rule types, ghost variables, hooks, and common verification patterns.

跳到安装

来源信息

仓库
ccashwell/evm-cortex
最近来源活动
2026年4月10日 16:31
检测到的 SKILL.md 语言
英语
星标
131
分支
18

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
formal-verification
description
Use when applying formal verification to Solidity contracts using Certora CVL or Halmos symbolic testing. Covers specification writing, rule types, ghost variables, hooks, and common verification patterns.
# Formal Verification for Solidity ## Tools | Tool | Approach | Strengths | |------|----------|-----------| | Certora Prover | CVL specs + SMT solving | Industry standard, deep analysis | | Halmos | Symbolic Foundry tests | Familiar Foundry interface, open source | | KEVM | K Framework semantics | EVM bytecode level verification | ## Certora CVL Specification Template ```cvl // spec/Vault.spec using ERC20 as token; methods { function deposit(uint256) external returns (uint256); function withdraw(uint256, address, address) external returns (uint256); function totalAssets() external returns (uint256) envfree; function totalSupply() external returns (uint256) envfree; function balanceOf(address) external returns (uint256) envfree; function asset() external returns (address) envfree; // Summarize external calls function _.transfer(address, uint256) external => DISPATCHER(true); function _.transferFrom(address, address, uint256) external => DISPATCHER(true); function _.balanceOf(address) external => DISPATCHER(true); } ``` ## Rule Types ### Parametric Rules Verify properties for all possible inputs: ```cvl // Depositing should increase total supply rule depositIncreasesSupply(uint256 assets) { env e; uint256 supplyBefore = totalSupply(); deposit(e, assets); uint256 supplyAfter = totalSupply(); assert supplyAfter >= supplyBefore, "supply must not decrease on deposit"; } ``` ### Invariant Rules Properties that must hold in every reachable state: ```cvl // Solvency: vault always has enough assets to back shares invariant solvency() totalSupply() == 0 || totalAssets() > 0 { preserved deposit(uint256 assets) with (env e) { require assets > 0; } } ``` ### Relational Rules Compare two executions: ```cvl // Monotonicity: depositing more gives more shares rule depositMonotonicity(uint256 assets1, uint256 assets2) { env e; require assets1 < assets2; storage init = lastStorage; uint256 shares1 = deposit(e, assets1); uint256 shares2 = deposit(e, assets2) at init; assert shares2 >= shares1, "more assets should give more shares"; } ``` ## Ghost Variables and Hooks Track state that isn't directly accessible: ```cvl ghost mathint sumOfBalances { init_state axiom sumOfBalances == 0; } hook Sstore balanceOf[KEY address user] uint256 newBalance (uint256 oldBalance) { sumOfBalances = sumOfBalances + newBalance - oldBalance; } invariant totalSupplyIsSumOfBalances() to_mathint(totalSupply()) == sumOfBalances; ``` ## Halmos Symbolic Testing Halmos runs Foundry tests symbolically — inputs are symbolic values, not concrete: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {Vault} from "../src/Vault.sol"; import {SymTest} from "halmos-cheatcodes/SymTest.sol"; contract VaultSymbolicTest is Test, SymTest { Vault vault; function setUp() public { vault = new Vault(address(token)); } /// @notice Verify deposit then withdraw returns at least original amount function check_depositWithdrawRoundTrip(uint256 assets) public { vm.assume(assets > 0 && assets < type(uint128).max); deal(address(token), address(this), assets); token.approve(address(vault), assets); uint256 shares = vault.deposit(assets, address(this)); uint256 received = vault.redeem(shares, address(this), address(this)); // Due to rounding, received should be <= assets assert(received <= assets); } /// @notice No share inflation from direct transfer function check_noShareInflation(uint256 donation) public { vm.assume(donation > 0 && donation < type(uint128).max); uint256 sharesBefore = vault.totalSupply(); // Direct transfer (donation attack) deal(address(token), address(vault), donation); uint256 sharesAfter = vault.totalSupply(); assert(sharesAfter == sharesBefore); } } ``` Run with: `halmos --contract VaultSymbolicTest` ## Common Verification Properties ### ERC20 Properties ```cvl rule transferIntegrity(address to, uint256 amount) { env e; address from = e.msg.sender; uint256 fromBefore = balanceOf(from); uint256 toBefore = balanceOf(to); transfer(e, to, amount); assert balanceOf(from) == fromBefore - amount; assert balanceOf(to) == toBefore + amount; } ``` ### Access Control ```cvl rule onlyOwnerCanPause() { env e; require e.msg.sender != owner(); pause@withrevert(e); assert lastReverted, "non-owner should not be able to pause"; } ``` ### No Ether Leak ```cvl invariant noEtherLeak() nativeBalances[currentContract] == 0; ``` ## Running Certora ```bash # Install pip install certora-cli # Run verification certoraRun src/Vault.sol \ --verify Vault:spec/Vault.spec \ --solc solc8.20 \ --optimistic_loop \ --loop_iter 3 \ --msg "Vault verification" ``` ## Checklist - [ ] Identify critical invariants before writing specs - [ ] Use `envfree` for view/pure functions (no environment needed) - [ ] Summarize external calls with `DISPATCHER` or `NONDET` - [ ] Ghost variables + hooks track aggregate state (sum of balances, etc.) - [ ] Test specs against known-buggy versions to verify they catch issues - [ ] Use `preserved` blocks in invariants to add preconditions - [ ] Halmos tests prefixed with `check_` (not `test_`) - [ ] Run with `--optimistic_loop` and appropriate `--loop_iter` - [ ] Review counterexamples in Certora's web UI for false positives
在 GitHub 查看