Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Replaced RPC mocking by a full block cache support. This allows users to cache responses from an RPC
node via `--cache-dir dir`.
- Changed `verify*` methods to always require postcodition.
- We now use a symbolic execution queue, so as not to run out of resources when there are
too many branches to explore.

## [0.56.0] - 2025-10-13

Expand Down
2 changes: 1 addition & 1 deletion bench/bench.hs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ runBCTest x =
findPanics :: App m => Solver -> Natural -> Integer -> ByteString -> m ()
findPanics solver count iters c = do
_ <- withSolvers solver count 1 Nothing $ \s -> do
let opts = defaultVeriOpts { iterConf = defaultIterConf {maxIter = Just iters, askSmtIters = iters + 1 }}
let opts = (defaultVeriOpts :: VeriOpts) { iterConf = defaultIterConf {maxIter = Just iters, askSmtIters = iters + 1 }}
checkAssert s allPanicCodes c Nothing [] opts
liftIO $ putStrLn "done"

Expand Down
21 changes: 12 additions & 9 deletions cli/cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import EVM.Concrete qualified as Concrete
import GitHash
import EVM.FeeSchedule (feeSchedule)
import EVM.Fetch qualified as Fetch
import EVM.Format (hexByteString, strip0x, formatExpr)
import EVM.Format (hexByteString, strip0x, formatExpr, indent)
import EVM.Solidity
import EVM.Solvers
import EVM.Stepper qualified
Expand Down Expand Up @@ -527,7 +527,7 @@ symbCheck cFileOpts sOpts cExecOpts cOpts = do
cache <- readMVar sess.sharedCache
Fetch.saveCache dir block cache
case res of
[Qed] -> do
[] -> do
liftIO $ putStrLn "\nQED: No reachable property violations discovered\n"
showExtras solvers sOpts calldata expr
_ -> do
Expand All @@ -540,26 +540,29 @@ symbCheck cFileOpts sOpts cExecOpts cOpts = do
, ""
] <> fmap (formatCex (fst calldata) Nothing) cexs
liftIO $ T.putStrLn $ T.unlines counterexamples
liftIO $ printWarnings Nothing [expr] res "symbolically"
liftIO $ printWarnings Nothing expr res "symbolically"
showExtras solvers sOpts calldata expr
liftIO exitFailure

showExtras :: App m => SolverGroup ->SymbolicOptions -> (Expr Buf, [Prop]) -> Expr End -> m ()
showExtras :: App m => SolverGroup ->SymbolicOptions -> (Expr Buf, [Prop]) -> [Expr End] -> m ()
showExtras solvers sOpts calldata expr = do
when sOpts.showTree $ liftIO $ do
putStrLn "=== Expression ===\n"
T.putStrLn $ formatExpr $ Expr.simplify expr
putStrLn "=== Leafs ===\n"
printLeafs $ map (formatExpr . Expr.simplify) expr
putStrLn ""
when sOpts.showReachableTree $ do
reached <- reachable solvers expr
liftIO $ do
putStrLn "=== Potentially Reachable Expression ===\n"
T.putStrLn (formatExpr . Expr.simplify $ reached)
putStrLn "=== Potentially Reachable Leafs ===\n"
printLeafs $ map (formatExpr . Expr.simplify) reached
putStrLn ""
when sOpts.getModels $ do
liftIO $ putStrLn $ "=== Models for " <> show (Expr.numBranches expr) <> " branches ==="
liftIO $ putStrLn $ "=== Models for " <> show (length expr) <> " branches ==="
ms <- produceModels solvers expr
liftIO $ forM_ ms (showModel (fst calldata))
where
printLeafs leafs =
T.putStrLn $ indent 2 $ T.unlines $ zipWith (\i r -> "Leaf " <> T.pack (show (i :: Integer)) <> ":\n" <> r) [0..] leafs

isTestOrLib :: Text -> Bool
isTestOrLib file = T.isSuffixOf ".t.sol" file || areAnyPrefixOf ["src/test/", "src/tests/", "lib/"] file
Expand Down
15 changes: 6 additions & 9 deletions doc/src/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,13 @@ Expressions in this language can have the following types:
- `Logs`: EVM logs

## Control Flow
An EVM program is represented by an `Expr End`, which is either a single end
state for a program without branches, or a series of nested if-then-else terms,
where each leaf is an end state. Some end states (e.g. `Return`) contain copies
of any externally observable data (i.e. returndata and post call storage).

As an example the following Expr encodes a program that branches based on the
equality of two symbolic words ("a" and "b"), and returns if they are equal and
reverts if they are not:
An EVM program is represented by an `[Expr End]`, which is a list of all
possible end states for a program without branches. As an example the following
two `Expr End`-s encode a program that branches based on the equality of two symbolic
words ("a" and "b"), and returns if they are equal and reverts if they are not:
```haskell
(ITE (Eq (Var "a") (Var "b")) (Success ...) (Failure ...))
[ Success [PEq (Var "a") (Var "b")] ...,
, Failure [PNeg (PEq (Var "a") (Var "b"))] ...]
```

## Buffers
Expand Down
2 changes: 0 additions & 2 deletions src/EVM.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2669,14 +2669,12 @@ traceForest' :: Expr End -> Forest Trace
traceForest' (Success _ (TraceContext f _ _) _ _) = f
traceForest' (Partial _ (TraceContext f _ _) _) = f
traceForest' (Failure _ (TraceContext f _ _) _) = f
traceForest' (ITE {}) = internalError"Internal Error: ITE does not contain a trace"
traceForest' (GVar {}) = internalError"Internal Error: Unexpected GVar"

traceContext :: Expr End -> TraceContext
traceContext (Success _ c _ _) = c
traceContext (Partial _ c _) = c
traceContext (Failure _ c _) = c
traceContext (ITE {}) = internalError"Internal Error: ITE does not contain a trace"
traceContext (GVar {}) = internalError"Internal Error: Unexpected GVar"

traceTopLog :: [Expr Log] -> EVM t s ()
Expand Down
16 changes: 0 additions & 16 deletions src/EVM/Expr.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,11 +1076,6 @@ simplifyNoLitToKeccak e = untilFixpoint (mapExpr go) e
| a == b = Lit 1
| otherwise = eq a b

-- redundant ITE
go (ITE (Lit x) a b)
| x == 0 = b
| otherwise = a

-- Masking as as per Solidity bit-packing of e.g. function parameters
go (And (Lit mask1) (Or (And (Lit mask2) _) x)) | (mask1 .&. mask2 == 0)
= And (Lit mask1) x
Expand Down Expand Up @@ -1228,13 +1223,6 @@ simplifyNoLitToKeccak e = untilFixpoint (mapExpr go) e
| b == (Lit 0) = a
| otherwise = EVM.Expr.or a b

-- If x is ever non zero the Or will always evaluate to some non zero value and the false branch will be unreachable
-- NOTE: with AND this does not work, because and(0x8, 0x4) = 0
go (ITE (Or (Lit x) a) t f)
| x == 0 = ITE a t f
| otherwise = t
go (ITE (Or a b@(Lit _)) t f) = ITE (Or b a) t f

-- we write at least 32, so if x <= 32, it's FALSE
go o@(EVM.Types.LT (BufLength (WriteWord {})) (Lit x))
| x <= 32 = Lit 0
Expand Down Expand Up @@ -1672,10 +1660,6 @@ max (Lit 0) y = y
max x (Lit 0) = x
max x y = normArgs Max Prelude.max x y

numBranches :: Expr End -> Int
numBranches (ITE _ t f) = numBranches t + numBranches f
numBranches _ = 1

allLit :: [Expr Byte] -> Bool
allLit = all isLitByte

Expand Down
14 changes: 5 additions & 9 deletions src/EVM/Format.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module EVM.Format
, showTraceTree'
, showValues
, prettyvmresult
, prettyvmresults
, showCall
, showWordExact
, showWordExplanation
Expand Down Expand Up @@ -201,7 +202,6 @@ showTraceTree dapp vm =
in pack $ concatMap showTree traces

showTraceTree' :: DappInfo -> Expr End -> Text
showTraceTree' _ (ITE {}) = internalError "ITE does not contain a trace"
showTraceTree' dapp leaf =
let ?context = DappContext { info = dapp, contracts, labels }
in let forest = traceForest' leaf
Expand Down Expand Up @@ -453,6 +453,10 @@ prettyvmresult (Failure _ _ err) = prettyError err
prettyvmresult (Partial _ _ p) = T.unpack $ formatPartial p
prettyvmresult r = internalError $ "Invalid result: " <> show r

prettyvmresults :: [Expr End] -> String
prettyvmresults results =
T.unpack $ indent 2 $ T.unlines $ zipWith (\i r -> T.pack ("Result " <> show (i :: Integer) <> ": " <> prettyvmresult r)) [0..] results

indent :: Int -> Text -> Text
indent n = rstrip . T.unlines . fmap (T.replicate n (T.pack [' ']) <>) . T.lines

Expand Down Expand Up @@ -565,14 +569,6 @@ formatExpr = go
(GVar v) -> "(GVar " <> T.pack (show v) <> ")"
LitByte w -> T.pack $ show w

ITE c t f -> T.unlines
[ "(ITE"
, indent 2 $ T.unlines
[ formatExpr c
, formatExpr t
, formatExpr f
]
, ")"]
Success asserts _ buf store -> T.unlines
[ "(Success"
, indent 2 $ T.unlines
Expand Down
1 change: 1 addition & 0 deletions src/EVM/Stepper.hs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ runFully = do
enter :: Text -> Stepper t s ()
enter t = evm (EVM.pushTrace (EntryTrace t))

-- Concrete interpretation
interpret
:: forall m a . (App m)
=> Fetch.Fetcher Concrete m RealWorld
Expand Down
Loading