We already had a static auditor. We already had a federation red team.
What we did not have was a way to throw a real exploit at the deployed contracts without risking the shared Anvil that lottery and ACEX keep using.
So we built DOLOS (Δόλος — guile): a dynamic EVM attack harness. Every exploit lands on a throwaway anvil --fork-url. When the fork is torn down, none of it ever existed.
Live: dolos.modelmarket.dev · Source: github.com/alexar76/dolos (MIT) · Capability: agent.security.contract-redteam@v1
The mistake we refused to make
The obvious test pattern is: attack the live chain, then evm_revert.
On our UNI bubble that is wrong. The Anvil is shared. A revert would silently discard real activity from the lottery relayer and the ACEX trader.
So DOLOS never writes the live chain. It spawns its own fork — a copy-on-write clone of live state. Every contract, balance, and storage slot is there to attack. Funded with fake ether. Drivable as any address. Disposable.
| Cheat code | What it buys | Why it is safe here |
|---|---|---|
anvil --fork-url |
Clone of live state | The clone is the target |
evm_snapshot / evm_revert
|
Total rollback between attacks | The disposable thing is the fork |
anvil_impersonateAccount |
Transact as owner / victim, no key | Impossible on a real chain — that is the point |
anvil_setBalance |
Fake gas for any address | None of it outlives the fork |
One sentence thesis:
Attack anything. Risk nothing. Because the target is a fork, not the bubble.
Don’t collapse the red teams
We already wrote about BASANOS (static assurance at a pinned commit) and MOMUS (HTTP / federation probes that cannot pay themselves).
DOLOS is the missing middle:
| Layer | Question |
|---|---|
| BASANOS | Is this Solidity sound at commit X? |
| DOLOS | Does the deployed contract hold under a concrete attack? |
| MOMUS | Is the live federation holey? |
BASANOS can flag "any caller can write channels[<caller>]". Only actually trying the hijack on a fork — and watching the guard refuse — proves whether that flag is a real hole or a false positive.
On the live UNI contracts, DOLOS refuted that flag. It tried. The guard held. It said so.
An attack is an invariant, not a poke
Not “call a function and see what happens.”
An invariant the contract claims. A transaction sequence that would break it. The ABI comes from the Foundry build — a missing ABI is inconclusive, never a made-up interface, never an assumed pass.
| Attack | Invariant | On real UNI contracts |
|---|---|---|
unauthorized_token_mint |
Supply grows only through an authorized minter | held |
escrow_channel_hijack |
An open channel id cannot be reopened by another caller | held |
lottery_operator_bypass |
Only OPERATOR_ROLE may open a round / withdraw fees |
held |
Held is a result. Not a gap.
Three first-class outcomes:
- held (200) — the contract refused. Honest negative. Can refute a static flag.
- exploited (561) — the invariant broke. Signed finding.
- inconclusive (520) — could not run. Never payable. Never “safe.”
Exit code 2 on a scan means we never reached the chain. That must never read as all-clear. We learned that the hard way elsewhere in the stack — silence is not green.
Findings use the same Ed25519 Finding / Verdict / Blame shape as MOMUS, so they drop into the same store, verifier, and Treasury. Scanner key signs. A different verifier key signs the verdict. Neither is the treasury key.
The fix loop — sandbox only
On a throwaway sandbox fork (chainId 31337), a confirmed hole can drive a fix:
attack → patch → forge test gate → redeploy → re-attack.
If the legitimate-behaviour suite goes red, the fix is rejected. A canary that refuses to be exploited aborts rather than claiming a fix.
On a real immutable contract the identical finding is tagged ADVISORY and stops at the report. Auto-fix there would be a critical bug of our own.
That is why DOLOS lives in UNI — the chain is disposable, so the fix loop is free and reversible.
Proven end to end on DolosCanary: a stranger drains 5 ETH it never deposited; the fixer inserts a balance guard; forge test stays green; the redeployed contract refuses the same drain; verdict fixed.
Real UNI contracts held. There was nothing to fix. That is the correct outcome.
Run it
git clone https://github.com/alexar76/dolos
cd dolos
pip install -e '.[dev,signing]'
pytest # 233 tests · 96% branch coverage with Foundry
# scan: fork the live UNI anvil, run the catalog, print signed findings
python -m dolos scan --fork-url http://127.0.0.1:8545 --key data/dolos_scanner_key
# canary: full attack → fix → retest → redeploy → re-attack
python -m dolos canary --standalone
| Command | 0 |
1 |
2 |
|---|---|---|---|
scan |
contracts held | non-by-design exploit found | could not run |
canary / fix
|
hole closed | not closed | — |
Optional: point DOLOS_FACTORY_FIX_URL at the Factory remediation endpoint. If the Factory is down, silent, or echoes its input, the built-in patcher takes over — never a false success.
What it is not
- Not a mainnet attack tool. Sandbox fork only for auto-fix.
- Not BASANOS. Static assurance is a different question.
- Not MOMUS. Federation probes are a different surface.
- Not insurance. Findings inform; they do not stake coverage.
- Not “AI red team theatre.” An optional LLM cannot invent attacks or rewrite verdicts — the catalog and the fork decide.
Soft close
If you keep collapsing “audit,” “scan,” “red team,” and “insurance” into one word, you will ship a critical with a clean conscience.
We needed a layer that could actually try — and still leave the live house alone.
That layer is DOLOS.
🌐 dolos.modelmarket.dev · 📦 github.com/alexar76/dolos
If this shape is useful, a ⭐ on the repo helps more than a polite nod in the comments.







Top comments (0)