Skip to content

Commit 4326ed0

Browse files
committed
Counterflow: AI-translated, Z3-proved smart contract invariant checking
Prove the contract, or reveal the exploit. - Z3 trusted core: allowances, transfers, vault shares, reentrancy, ghost-sum accounting - LLM → binding translation (OpenAI/DeepSeek, temperature 0) - Slither AST extraction + binding diff engine for auto-review - Halmos bytecode symbolic execution - Echidna/Medusa property-fuzz test generation - Foundry invariant test export (Certora/Kontrol/Medusa/Halmos compatible) - 6-exploit benchmark suite + DeFiHackLabs runner - 11 e2e tests, 12 CLI commands - MIT open core
1 parent b587a51 commit 4326ed0

44 files changed

Lines changed: 3993 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ whitepaper-open-interop-layer.md
4040
proofpack/
4141
veros-core/
4242
veros-actuarial/
43-
veros-verify/
4443
opencontext/
4544
openchart/
4645
mcp-observatory-clone/

veros-verify/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.venv/
2+
node_modules/
3+
audit.jsonl
4+
binding.json
5+
halmos/out/
6+
halmos/cache/
7+
echidna-output/
8+
foundry-output/
9+
__pycache__/

veros-verify/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 KryptosAI
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

veros-verify/README.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# <picture><img src="assets/logo.png" height="48" align="left" alt="Counterflow logo"/></picture>Counterflow
2+
3+
> **Prove the contract, or reveal the exploit.**
4+
5+
AI-translated, machine-proved smart contract invariant checking — three layers:
6+
7+
1. **Z3 abstract model** — inductive proofs over a fixed vocabulary (allowances, transfers, vault shares, ghost-sum accounting). 6 benchmark exploit classes, all caught.
8+
2. **Halmos bytecode** — symbolic execution against compiled EVM bytecode, closing the spec-vs-implementation gap.
9+
3. **LLM translation** — Solidity + English → structured binding (the only untrusted layer; never decides the verdict).
10+
11+
**The LLM never decides the verdict.** A ~350-line, human-auditable Z3 core either *proves*
12+
the invariant holds for **all** inputs, or produces a **concrete
13+
counterexample** (an exploit trace). Hallucination is structurally contained:
14+
a bad translation is caught by deterministic vocabulary validation or produces a wrong
15+
model — never a false proof of the trusted core's semantics.
16+
17+
```
18+
Solidity + English invariants
19+
20+
21+
[Slither extraction] structural AST pass — function selectors, state vars
22+
23+
24+
[LLM translate] untrusted — DeepSeek/OpenAI, temperature 0
25+
26+
27+
binding.json human-reviewable artifact (the real spec)
28+
29+
30+
[validate] deterministic vocabulary/schema gate
31+
32+
├─────────────────────────────────────────┬──────────────────────────┐
33+
▼ ▼ ▼
34+
[Z3 inductive check] [Halmos bytecode] [Echidna validation]
35+
TRUSTED — abstract model TRUSTED — EVM symbolic exec fuzzing harness gen
36+
│ │ │
37+
▼ ▼ ▼
38+
PROVED | VIOLATED (+ cex) PASS | FAIL (+ cex calldata) echidna test file
39+
│ │ │
40+
└───────────────┬─────────────────────────┴──────────────────────────┘
41+
42+
audit.jsonl SHA-256 hash-chained, tamper-evident run log
43+
44+
45+
[Foundry export] renderSolidity → handler + invariants .sol files
46+
```
47+
48+
## Quickstart
49+
50+
```bash
51+
# —— deterministic (no LLM needed) ——
52+
node src/cli.js check examples/TokenPool.binding.json # PROVED
53+
node src/cli.js check examples/TokenPoolBuggy.binding.json # VIOLATED + exploit
54+
55+
# —— full AI pipeline (needs DEEPSEEK_API_KEY or OPENAI_API_KEY) ——
56+
node src/cli.js verify examples/TokenPoolBuggy.sol examples/invariants.txt
57+
58+
# —— two-step, human-in-the-loop (recommended) ——
59+
node src/cli.js extract examples/TokenPool.sol examples/invariants.txt -o binding.json
60+
# review binding.json ...
61+
node src/cli.js check binding.json
62+
63+
# —— bytecode-level (needs foundry + halmos) ——
64+
pip install z3-solver halmos # or use project .venv
65+
node src/cli.js bytecode HalmosTest
66+
67+
# —— everything ——
68+
npm run all
69+
70+
# —— audit chain ——
71+
node src/cli.js audit
72+
```
73+
74+
Requires Node >= 18, Python 3 with `z3-solver` and `halmos`, and [Foundry](https://getfoundry.sh/) (`brew install foundry`). Set `COUNTERFLOW_PYTHON` to a venv python if needed.
75+
76+
## Audit Binding
77+
78+
```bash
79+
# —— audit-binding: review & compare binding snapshots ——
80+
node src/cli.js audit-binding binding.json # show binding structure & validation
81+
node src/cli.js audit-binding a.json b.json # diff two bindings side-by-side
82+
```
83+
84+
Audit-binding loads one or two binding files and produces a human-readable summary:
85+
- Validates the binding vocabulary and schema
86+
- Lists all functions, guards, effects, and invariants
87+
- When given two bindings, computes a structural diff showing added/removed/changed
88+
functions, guards, effects, and invariants
89+
- Useful in CI to detect unintended binding drift across revisions
90+
91+
## Export
92+
93+
```bash
94+
# —— generate Echidna fuzzing harness ——
95+
node src/cli.js gen-echidna binding.json > EchidnaTest.sol
96+
# produces: contract EchidnaBindingTest with echidna_ prefixed properties
97+
98+
# —— generate Foundry invariant test files ——
99+
node src/cli.js gen-foundry binding.json -o foundry-tests/
100+
# produces: handler .sol + invariants .sol with function invariant_ stubs
101+
```
102+
103+
Export commands generate derivative artifacts from a binding:
104+
- **Echidna**: produces a standalone Solidity file with `echidna_`-prefixed property
105+
functions ready for `echidna-test`. Each invariant becomes an Echidna property.
106+
- **Foundry**: produces `{Model}Handler.sol` (actor management) and
107+
`{Model}Invariants.sol` (per-invariant `function invariant_*` stubs) for use
108+
with `forge test` invariant testing.
109+
110+
## DeFiHackLabs Benchmark
111+
112+
The defihack runner executes Counterflow against the
113+
[DeFiHackLabs](https://github.com/SunWeb3Sec/DeFiHackLabs) exploit corpus:
114+
115+
```bash
116+
node bench/defihack.js # run all cases
117+
node bench/defihack.js --case cream-finance # single case
118+
```
119+
120+
Each case:
121+
1. Loads the Solidity source and English invariants from the corpus
122+
2. Runs LLM translation → binding validation → Z3 proof
123+
3. Compares the verdict against the known exploit label
124+
4. Produces a per-case report (contract, invariants, verdict, counterexample)
125+
126+
This validates that the pipeline catches real-world DeFi exploits, not just
127+
synthetic examples. Current coverage includes CREAM Finance, Hundred Finance,
128+
and other high-profile incidents.
129+
130+
## Benchmark
131+
132+
```
133+
npm run bench
134+
# or: npm run all (includes e2e tests + bytecode check)
135+
136+
6/6 cases correct:
137+
138+
TokenPool (safe) PROVED [reference]
139+
TokenPoolBuggy (missing balance check) VIOLATED [underflow-drain]
140+
SafeVault (safe) PROVED [reference]
141+
ApprovalDrain (transferFrom no check) VIOLATED [approval-drain]
142+
UnbackedMintVault (owner infinite mint) VIOLATED [unbacked-mint]
143+
BurnDesyncVault (totalShares desync) VIOLATED [accounting-desync]
144+
145+
Bytecode: Halmos symbolic test
146+
✓ TokenPool withdraw no underflow PASS
147+
✗ TokenPoolBuggy withdraw no underflow FAIL (amt > pre counterexample)
148+
```
149+
150+
## State model (v2)
151+
152+
| Vocabulary | Covers |
153+
|---|---|
154+
| `balances[a]`, `shares[a]`, `allowances[a]` | ERC20 + ERC4626 + approvals |
155+
| `totalAssets`, `totalShares` | pool totals |
156+
| ghost sums: `sumBalances`, `sumShares` | exact accounting integrity |
157+
| 9 invariants | nonneg_balance/shares/allowance/total/total_shares, solvency, shares_integrity, backing, supply_cap |
158+
| 9 guards, 13 effects | deposit/withdraw/mint/burn/transfer/transferFrom/approve |
159+
160+
### Reentrancy
161+
162+
The reentrancy vocabulary models the standard check-effects-interaction pattern
163+
with a reentrancy lock:
164+
165+
| Vocabulary | Covers |
166+
|---|---|
167+
| guard `not_locked` | `require(!locked)` — the function's reentrancy lock is free |
168+
| effect `reentrancy_lock_acquire` | `locked = true` |
169+
| effect `reentrancy_lock_release` | `locked = false` |
170+
| effect `external_call` | marker for an external call — snapshots state for reentrancy checks |
171+
| invariant `reentrancy_safe` | if in external call, storage (total, sumBalances) has not changed since the snapshot |
172+
173+
A properly modeled withdraw with reentrancy:
174+
175+
```json
176+
{
177+
"name": "withdraw",
178+
"guards": ["amt_gt_0", "bal_ge_amt", "not_locked"],
179+
"effects": ["reentrancy_lock_acquire", "bal_sub_amt", "total_sub_amt",
180+
"external_call", "reentrancy_lock_release"]
181+
}
182+
```
183+
184+
The Z3 core tracks per-function locks, an `in_call` flag, and storage snapshots
185+
taken at the `external_call` point. The `reentrancy_safe` invariant is violated if
186+
any state variable changes between the snapshot and the end of the call — catching
187+
cross-function reentrancy even when locks appear to be used.
188+
189+
## What a verdict means (read this)
190+
191+
- **PROVED** — the modeled transition preserves the invariant for *all*
192+
possible inputs and states (an inductive proof over the reviewed binding).
193+
It is **not** a claim that the contract is "safe": the binding is an
194+
abstraction and must be reviewed; bytecode verification closes the gap.
195+
- **VIOLATED** — Z3 or Halmos found a concrete counterexample.
196+
- **UNKNOWN** — the solver could not decide within limits. Nothing is claimed.
197+
198+
## Open core
199+
200+
MIT-licensed: CLI, translation prompts, validation, the trusted Z3 core,
201+
Halmos symbolic tests, and example bindings. Commercial layer (separate):
202+
hosted pipeline, CI integration, dashboards, proof storage, multi-contract
203+
projects.
204+
205+
## Roadmap
206+
207+
- CVL export for Certora interop
208+
- Richer state model: reentrancy flags, compound interest/accrual
209+
- L2/bridge message verification targets
210+
- Public leaderboard of verified contracts

veros-verify/assets/logo.png

2.58 MB
Loading
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"model": "erc20_pool",
3+
"functions": [
4+
{
5+
"name": "transferFrom",
6+
"guards": ["bal_src_ge_amt"],
7+
"effects": ["allowance_sub_amt", "bal_sub_amt_src", "bal_add_amt_to"]
8+
}
9+
],
10+
"invariants": ["nonneg_balance", "nonneg_allowance", "solvency", "nonneg_total"],
11+
"invariant_mapping": [
12+
{ "english": "A spender can never move more than they were approved for.", "invariant": "nonneg_allowance" },
13+
{ "english": "A user's balance can never go negative.", "invariant": "nonneg_balance" },
14+
{ "english": "User balances always sum to totalAssets.", "invariant": "solvency" }
15+
]
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"model": "erc20_pool",
3+
"functions": [
4+
{
5+
"name": "deposit",
6+
"guards": ["amt_gt_0"],
7+
"effects": ["shares_add_amt", "total_shares_add_amt", "total_add_amt"]
8+
},
9+
{
10+
"name": "burn",
11+
"guards": ["shares_ge_amt", "total_ge_amt"],
12+
"effects": ["shares_sub_amt", "total_sub_amt"]
13+
}
14+
],
15+
"invariants": ["nonneg_shares", "nonneg_total_shares", "nonneg_total", "shares_integrity"],
16+
"invariant_mapping": [
17+
{ "english": "User shares always sum to the total share supply.", "invariant": "shares_integrity" },
18+
{ "english": "The share supply can never go negative.", "invariant": "nonneg_total_shares" }
19+
]
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"model": "erc20_pool",
3+
"functions": [
4+
{
5+
"name": "deposit",
6+
"guards": ["amt_gt_0"],
7+
"effects": ["shares_add_amt", "total_shares_add_amt", "total_add_amt"]
8+
},
9+
{
10+
"name": "withdraw",
11+
"guards": ["amt_gt_0", "shares_ge_amt", "total_shares_ge_amt", "total_ge_amt"],
12+
"effects": ["shares_sub_amt", "total_shares_sub_amt", "total_sub_amt"]
13+
}
14+
],
15+
"invariants": ["nonneg_shares", "nonneg_total_shares", "nonneg_total", "shares_integrity", "backing"],
16+
"invariant_mapping": [
17+
{ "english": "Every share is backed by at least one asset.", "invariant": "backing" },
18+
{ "english": "User shares always sum to the total share supply.", "invariant": "shares_integrity" }
19+
]
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"model": "erc20_pool",
3+
"functions": [
4+
{
5+
"name": "deposit",
6+
"guards": ["amt_gt_0"],
7+
"effects": ["shares_add_amt", "total_shares_add_amt", "total_add_amt"]
8+
},
9+
{
10+
"name": "ownerMint",
11+
"guards": ["sender_is_owner"],
12+
"effects": ["shares_add_amt", "total_shares_add_amt"]
13+
}
14+
],
15+
"invariants": ["nonneg_shares", "nonneg_total_shares", "nonneg_total", "shares_integrity", "backing"],
16+
"invariant_mapping": [
17+
{ "english": "Every share is backed by at least one asset.", "invariant": "backing" },
18+
{ "english": "User shares always sum to the total share supply.", "invariant": "shares_integrity" }
19+
]
20+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
/// @title ApprovalDrain — transferFrom decrements the allowance inside an
5+
/// unchecked block WITHOUT checking it first. Any approved spender (even for
6+
/// 1 wei) can move the victim's entire balance: the classic
7+
/// missing-allowance-check exploit.
8+
contract ApprovalDrain {
9+
uint256 public totalAssets;
10+
mapping(address => uint256) public balances;
11+
mapping(address => mapping(address => uint256)) public allowances;
12+
13+
function approve(address spender, uint256 amt) external {
14+
allowances[msg.sender][spender] = amt;
15+
}
16+
17+
function transferFrom(address src, address to, uint256 amt) external {
18+
// BUG: allowance never validated before decrement, and unchecked math
19+
unchecked {
20+
allowances[src][msg.sender] -= amt;
21+
}
22+
balances[src] -= amt; // checked: implicit balances[src] >= amt guard
23+
balances[to] += amt;
24+
}
25+
}

0 commit comments

Comments
 (0)