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で見る