Halmos
A symbolic testing tool for Ethereum smart contracts that runs Foundry tests with symbolic inputs to prove properties hold for all possible values.
Halmos is a symbolic execution tool for Ethereum smart contracts developed by a]]16z. Unlike traditional testing that checks specific inputs or fuzzing that explores random inputs, Halmos uses symbolic values to mathematically verify that properties hold for all possible inputs. It integrates seamlessly with Foundry test suites, allowing developers to upgrade existing fuzz tests into formal proofs with minimal changes.
How Halmos Works
Halmos interprets Foundry tests symbolically rather than concretely:
1// Traditional fuzz test - tests random values2function testFuzz_withdraw(uint256 amount) public {3 // Tested with random values like 0, 1, 100, type(uint256).max4 vm.assume(amount <= balance);5 vault.withdraw(amount);6 assertEq(vault.balance(), balance - amount);7}89// Same test with Halmos - proves for ALL values10// Halmos treats 'amount' as symbolic, exploring all possibilities
When you run halmos, it:
- Assigns symbolic values to function parameters
- Executes the test symbolically, tracking constraints
- Uses SMT solvers to check if any input can violate assertions
- Reports counterexamples if violations exist, or confirms the property holds
Installation and Usage
1# Install via pip2pip install halmos34# Run on a Foundry project5cd your-project6halmos78# Run specific test9halmos --function testSymbolic_withdraw1011# With loop bounds (handle loops)12halmos --loop 10
Halmos automatically detects test files and runs functions matching its naming conventions.
Writing Halmos Tests
Halmos tests look like Foundry tests with a few conventions:
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.0;34import "forge-std/Test.sol";5import "../src/Token.sol";67contract TokenSymbolicTest is Test {8 Token token;910 function setUp() public {11 token = new Token();12 token.mint(address(this), 1000);13 }1415 // Prefix with 'check_' for symbolic tests16 function check_transferPreservesTotalSupply(17 address to,18 uint256 amount19 ) public {20 // Skip invalid cases21 vm.assume(to != address(0));22 vm.assume(amount <= token.balanceOf(address(this)));2324 uint256 supplyBefore = token.totalSupply();2526 token.transfer(to, amount);2728 // This assertion is checked for ALL valid inputs29 assertEq(token.totalSupply(), supplyBefore);30 }31}
The check_ prefix tells Halmos to analyze this function symbolically.
Halmos vs Fuzzing vs Unit Tests
| Aspect | Unit Tests | Fuzzing | Halmos |
|---|---|---|---|
| Inputs | Manual, specific | Random, many | Symbolic, all |
| Coverage | What you write | Statistical | Mathematical |
| Confidence | Low | Medium-High | Provable |
| Speed | Fast | Medium | Slower |
| Setup | Minimal | Minimal | Same as fuzz |
Halmos provides the highest confidence level—when it passes, the property is mathematically proven for all inputs within the explored bounds.
Practical Example: Verifying Token Invariants
1contract TokenInvariantTest is Test {2 Token token;3 address[] users;45 function setUp() public {6 token = new Token();7 users.push(address(0x1));8 users.push(address(0x2));9 users.push(address(0x3));1011 // Initial distribution12 token.mint(users[0], 500);13 token.mint(users[1], 300);14 token.mint(users[2], 200);15 }1617 // Prove: sum of balances always equals total supply18 function check_balancesSumToSupply(19 uint256 fromIdx,20 uint256 toIdx,21 uint256 amount22 ) public {23 vm.assume(fromIdx < users.length);24 vm.assume(toIdx < users.length);25 vm.assume(fromIdx != toIdx);2627 address from = users[fromIdx];28 address to = users[toIdx];2930 vm.assume(amount <= token.balanceOf(from));3132 vm.prank(from);33 token.transfer(to, amount);3435 // Calculate sum of all balances36 uint256 sum = 0;37 for (uint i = 0; i < users.length; i++) {38 sum += token.balanceOf(users[i]);39 }4041 // Prove this holds for ALL valid transfers42 assertEq(sum, token.totalSupply());43 }44}
If Halmos completes without finding counterexamples, you've mathematically proven this invariant holds for any valid transfer.
Handling Loops and Bounds
Symbolic execution faces the path explosion problem with loops. Halmos handles this with bounded analysis:
1# Analyze loops up to 5 iterations2halmos --loop 534# Increase for more thorough analysis (slower)5halmos --loop 20
For loops with known bounds, this provides complete coverage. For unbounded loops, you get guarantees up to the specified bound.
When Halmos Finds a Bug
When a property can be violated, Halmos outputs a counterexample:
1[FAIL] check_withdrawNeverExceedsBalance(uint256)2Counterexample:3 amount = 100000000000000000000000000145Call trace:6 withdraw(1000000000000000000000000001)7 -> balance underflow
This concrete counterexample helps developers understand exactly how the vulnerability can be triggered.
Limitations
Loop bounds: Unbounded loops require setting explicit bounds, providing guarantees only up to that bound.
External calls: Calls to external contracts are difficult to reason about symbolically without modeling those contracts.
Complex math: Some operations (keccak256, ecrecover) are treated as uninterpreted functions, limiting reasoning about their results.
Computation time: Complex contracts with many paths can take significant time to analyze.
Integration with Security Workflows
Halmos fits into a layered security approach:
- Unit tests: Fast feedback during development
- Fuzz tests: Broad coverage with random exploration
- Halmos: Mathematical proofs for critical properties
- Manual audit: Human review for business logic and design
For high-value contracts, Halmos provides the mathematical certainty that fuzzing approximates but can't guarantee. Major protocols like Uniswap and Aave use symbolic verification for their most critical code paths.
Comparison with Other Formal Verification Tools
| Tool | Approach | Integration | Learning Curve |
|---|---|---|---|
| Halmos | Symbolic testing | Foundry native | Low |
| Kontrol | K framework | Foundry compatible | Medium |
| Certora | Custom spec language | Separate specs | Higher |
| HEVM | Symbolic EVM | Foundry compatible | Medium |
Halmos's strength is its low barrier to entry—if you can write Foundry tests, you can write Halmos tests.
Articles Using This Term
Learn more about Halmos in these articles:
Related Terms
Symbolic Execution
A program analysis technique that uses symbolic values instead of concrete inputs to explore all possible execution paths simultaneously.
Formal Verification
Mathematical proof technique using symbolic logic to verify smart contract invariants cannot be violated under any conditions.
Foundry
Fast, portable Ethereum development framework written in Rust, featuring advanced testing and debugging capabilities.
Invariant
A property or condition that must always hold true throughout a smart contract's execution, used as a basis for testing and formal verification.
Need expert guidance on Halmos?
Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.
Get a Quote

