Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration in a monadic way, for now used only to configure SMT2 debugging #405

Merged
merged 29 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
14da0b7
The SMT2 file ought to be printed while executing it
msooseth Oct 9, 2023
512d6c1
Adding Monadic config
msooseth Oct 9, 2023
1b9c89c
Adding changelog
msooseth Oct 9, 2023
1662425
Fixing rebasing
msooseth Oct 9, 2023
a3e0e6d
Fixing up bench
msooseth Oct 9, 2023
2b28a25
Fixing up rpc
msooseth Oct 9, 2023
f394589
Don't dump queries during BlockchainTests
msooseth Oct 9, 2023
9b20526
Cleaner setup
msooseth Oct 10, 2023
43f79d5
Not so much debug
msooseth Oct 10, 2023
0064936
Fixing bench
msooseth Oct 11, 2023
3b05c85
Also add abstraction-refinement
msooseth Oct 11, 2023
03fea0e
Fixing up some minor things
msooseth Oct 11, 2023
a6bb132
Adding tracing to places
msooseth Oct 12, 2023
6456455
Fixing imports
msooseth Oct 13, 2023
0aa1d08
Refactor to make it more readable by d-xo
msooseth Oct 13, 2023
69c391f
Cleaner monadic patterns
msooseth Oct 13, 2023
072821f
Removing useless imports
msooseth Oct 13, 2023
bcd8573
All config options in test.hs
msooseth Oct 16, 2023
f16c8f6
Cleanup
msooseth Oct 16, 2023
55552ba
Merge branch 'main' into fix-debugging-smt2-addreaderT-squashed-2
msooseth Oct 24, 2023
65b44a9
Fixing up merge
msooseth Oct 24, 2023
2f0b85e
Fixing up after the merge
msooseth Oct 24, 2023
63ad6c4
Improving naming
msooseth Oct 24, 2023
1b57a20
Removing noise
msooseth Oct 24, 2023
8a3e60f
Allowing verbosity control through cli
msooseth Oct 25, 2023
016f3ec
Fixing verbosity
msooseth Oct 25, 2023
779675b
Fixing build
msooseth Oct 25, 2023
6e8fdca
Update src/EVM/Fetch.hs
msooseth Oct 25, 2023
55c1170
Not reusing the `--verbose` flag
msooseth Oct 26, 2023
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ dist/
dist-newstyle/
.configured
cabal.project.local*

# debug files
*.smt2
*.prop
*.expr
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- CopySlice+WriteWord+ConcreteBuf now truncates ConcreteBuf in special cases
- Better simplification of Eq IR elements
- Run a toplevel constant folding reasoning system on branch conditions
- Global config via a monad, which should allow for more refactoring
- `evalProp` is renamed to `simplifyProp` for consistency
- Mem explosion in `writeWord` function was possible in case `offset` was close to 2^256. Fixed.
- BufLength was not simplified via bufLength function. Fixed.
Expand Down
18 changes: 11 additions & 7 deletions bench/bench.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Main where

import GHC.Natural
import Control.Monad
import Control.Monad.IO.Unlift
import Data.Maybe
import System.Environment (getEnv)

Expand All @@ -19,6 +20,7 @@ import qualified Data.ByteString.Lazy as LazyByteString
import EVM.SymExec
import EVM.Solidity
import EVM.Solvers
import EVM.Effects
import EVM.Format (hexByteString)
import qualified EVM.Stepper as Stepper
import qualified EVM.Fetch as Fetch
Expand Down Expand Up @@ -67,32 +69,34 @@ blockchainTests ts = bench "blockchain-tests" $ nfIO $ do
if n `elem` ignored
then pure True
else do
res <- runBCTest c
res <- runApp $ runBCTest c
pure $ acc && res
) True cases

-- | executes a single test case and returns a boolean value representing its success
runBCTest :: BCTests.Case -> IO Bool
runBCTest :: App m => BCTests.Case -> m Bool
runBCTest x =
do
vm0 <- BCTests.vmForCase x
vm0 <- liftIO $ BCTests.vmForCase x
result <- Stepper.interpret (Fetch.zero 0 Nothing) vm0 Stepper.runFully
writeTrace vm0

maybeReason <- BCTests.checkExpectation False x result
pure $ isNothing maybeReason


--- Helpers ----------------------------------------------------------------------------------------


findPanics :: Solver -> Natural -> Integer -> ByteString -> IO ()
findPanics :: App m => Solver -> Natural -> Integer -> ByteString -> m ()
findPanics solver count iters c = do
_ <- withSolvers solver count Nothing $ \s -> do
let opts = defaultVeriOpts
{ maxIter = Just iters
, askSmtIters = iters + 1
}
checkAssert s allPanicCodes c Nothing [] opts
putStrLn "done"
liftIO $ putStrLn "done"


-- constructs a benchmark suite that checks the given bytecode for reachable
Expand All @@ -103,8 +107,8 @@ mkbench :: IO ByteString -> String -> Integer -> [Natural] -> Benchmark
mkbench c name iters counts = localOption WallTime $ env c (bgroup name . bmarks)
where
bmarks c' = concat $ [
[ bench ("cvc5-" <> show i) $ nfIO $ findPanics CVC5 i iters c'
, bench ("z3-" <> show i) $ nfIO $ findPanics Z3 i iters c'
[ bench ("cvc5-" <> show i) $ nfIO $ runApp $ findPanics CVC5 i iters c'
, bench ("z3-" <> show i) $ nfIO $ runApp $ findPanics Z3 i iters c'
]
| i <- counts
]
Expand Down
100 changes: 55 additions & 45 deletions cli/cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Main where

import Control.Monad (when, forM_, unless)
import Control.Monad.ST (RealWorld, stToIO)
import Control.Monad.IO.Unlift
import Data.ByteString (ByteString)
import Data.DoubleWord (Word256)
import Data.List (intersperse)
Expand All @@ -22,7 +23,6 @@ import Optics.Core ((&), set)
import Witch (unsafeInto)
import Options.Generic as Options
import Paths_hevm qualified as Paths
import System.IO (stderr)
import System.Directory (withCurrentDirectory, getCurrentDirectory, doesDirectoryExist)
import System.FilePath ((</>))
import System.Exit (exitFailure, exitWith, ExitCode(..))
Expand All @@ -35,14 +35,15 @@ import EVM.Concrete qualified as Concrete
import GitHash
import EVM.FeeSchedule (feeSchedule)
import EVM.Fetch qualified as Fetch
import EVM.Format (hexByteString, strip0x, showTraceTree, formatExpr)
import EVM.Format (hexByteString, strip0x, formatExpr)
import EVM.Solidity
import EVM.Solvers
import EVM.Stepper qualified
import EVM.SymExec
import EVM.Transaction qualified
import EVM.Types hiding (word)
import EVM.Types hiding (word, Env)
import EVM.UnitTest
import EVM.Effects

-- This record defines the program's command-line options
-- automatically via the `optparse-generic` package.
Expand Down Expand Up @@ -85,6 +86,8 @@ data Command w
, maxIterations :: w ::: Maybe Integer <?> "Number of times we may revisit a particular branching point"
, solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc5"
, smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"
, debug :: w ::: Bool <?> "Debug printing of internal behaviour"
, trace :: w ::: Bool <?> "Dump trace"
, assertions :: w ::: Maybe [Word256] <?> "Comma separated list of solc panic codes to check for (default: user defined assertion violations only)"
, askSmtIterations :: w ::: Integer <!> "1" <?> "Number of times we may revisit a particular branching point before we consult the smt solver to check reachability (default: 1)"
, numSolvers :: w ::: Maybe Natural <?> "Number of solver instances to use (default: number of cpu cores)"
Expand All @@ -103,6 +106,8 @@ data Command w
, solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc5"
, smtoutput :: w ::: Bool <?> "Print verbose smt output"
, smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"
, debug :: w ::: Bool <?> "Debug printing of internal behaviour"
, trace :: w ::: Bool <?> "Dump trace"
, askSmtIterations :: w ::: Integer <!> "1" <?> "Number of times we may revisit a particular branching point before we consult the smt solver to check reachability (default: 1)"
, loopDetectionHeuristic :: w ::: LoopHeuristic <!> "StackBased" <?> "Which heuristic should be used to determine if we are in a loop: StackBased (default) or Naive"
, abstractArithmetic :: w ::: Bool <?> "Use abstraction-refinement for complicated arithmetic functions such as MulMod. This runs the solver first with abstraction turned on, and if it returns a potential counterexample, the counterexample is refined to make sure it is a counterexample for the actual (not the abstracted) problem"
Expand All @@ -128,6 +133,7 @@ data Command w
, maxcodesize :: w ::: Maybe W256 <?> "Block: max code size"
, prevRandao :: w ::: Maybe W256 <?> "Block: prevRandao"
, chainid :: w ::: Maybe W256 <?> "Env: chainId"
, debug :: w ::: Bool <?> "Debug printing of internal behaviour"
, trace :: w ::: Bool <?> "Dump trace"
, rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"
, block :: w ::: Maybe W256 <?> "Block state is be fetched from"
Expand All @@ -144,6 +150,8 @@ data Command w
, match :: w ::: Maybe String <?> "Test case filter - only run methods matching regex"
, solver :: w ::: Maybe Text <?> "Used SMT solver: z3 (default) or cvc5"
, smtdebug :: w ::: Bool <?> "Print smt queries sent to the solver"
, debug :: w ::: Bool <?> "Debug printing of internal behaviour"
, trace :: w ::: Bool <?> "Dump trace"
, ffi :: w ::: Bool <?> "Allow the usage of the hevm.ffi() cheatcode (WARNING: this allows test authors to execute arbitrary code on your machine)"
, smttimeout :: w ::: Maybe Natural <?> "Timeout given to SMT solver in seconds (default: 300)"
, maxIterations :: w ::: Maybe Integer <?> "Number of times we may revisit a particular branching point"
Expand Down Expand Up @@ -186,54 +194,58 @@ getFullVersion = showVersion Paths.version <> " [" <> gitVersion <> "]"
main :: IO ()
main = do
cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator"
let env = Env { config = defaultConfig
{ dumpQueries = cmd.smtdebug
, debug = cmd.debug
, abstRefineMem = cmd.abstractMemory
, abstRefineArith = cmd.abstractArithmetic
, dumpTrace = cmd.trace
} }
case cmd of
Version {} ->putStrLn getFullVersion
Symbolic {} -> do
root <- getRoot cmd
withCurrentDirectory root $ assert cmd
Equivalence {} -> equivalence cmd
Exec {} ->
launchExec cmd
withCurrentDirectory root $ runEnv env $ assert cmd
Equivalence {} -> runEnv env $ equivalence cmd
Exec {} -> runEnv env $ launchExec cmd
Test {} -> do
root <- getRoot cmd
withCurrentDirectory root $ do
cores <- unsafeInto <$> getNumProcessors
solver <- getSolver cmd
withSolvers solver cores cmd.smttimeout $ \solvers -> do
buildOut <- readBuildOutput root (getProjectType cmd)
runEnv env $ withSolvers solver cores cmd.smttimeout $ \solvers -> do
buildOut <- liftIO $ readBuildOutput root (getProjectType cmd)
case buildOut of
Left e -> do
Left e -> liftIO $ do
putStrLn $ "Error: " <> e
exitFailure
Right out -> do
-- TODO: which functions here actually require a BuildOutput, and which can take it as a Maybe?
testOpts <- unitTestOptions cmd solvers (Just out)
testOpts <- liftIO $ unitTestOptions cmd solvers (Just out)
res <- unitTest testOpts out.contracts
unless res exitFailure
liftIO $ unless res exitFailure

equivalence :: Command Options.Unwrapped -> IO ()
equivalence :: App m => Command Options.Unwrapped -> m ()
equivalence cmd = do
let bytecodeA = hexByteString "--code" . strip0x $ cmd.codeA
bytecodeB = hexByteString "--code" . strip0x $ cmd.codeB
veriOpts = VeriOpts { simp = True
, debug = False
, maxIter = cmd.maxIterations
, askSmtIters = cmd.askSmtIterations
, loopHeuristic = cmd.loopDetectionHeuristic
, abstRefineConfig = AbstRefineConfig cmd.abstractArithmetic cmd.abstractMemory
, rpcInfo = Nothing
}
calldata <- buildCalldata cmd
solver <- getSolver cmd
calldata <- liftIO $ buildCalldata cmd
solver <- liftIO $ getSolver cmd
withSolvers solver 3 Nothing $ \s -> do
res <- equivalenceCheck s bytecodeA bytecodeB veriOpts calldata
case any isCex res of
False -> do
False -> liftIO $ do
putStrLn "No discrepancies found"
when (any isTimeout res) $ do
putStrLn "But timeout(s) occurred"
exitFailure
True -> do
True -> liftIO $ do
let cexs = mapMaybe getCex res
T.putStrLn . T.unlines $
[ "Not equivalent. The following inputs result in differing behaviours:"
Expand Down Expand Up @@ -289,29 +301,27 @@ buildCalldata cmd = case (cmd.calldata, cmd.sig) of


-- If function signatures are known, they should always be given for best results.
assert :: Command Options.Unwrapped -> IO ()
assert :: App m => Command Options.Unwrapped -> m ()
assert cmd = do
let block' = maybe Fetch.Latest Fetch.BlockNumber cmd.block
rpcinfo = (,) block' <$> cmd.rpc
calldata <- buildCalldata cmd
preState <- symvmFromCommand cmd calldata
calldata <- liftIO $ buildCalldata cmd
preState <- liftIO $ symvmFromCommand cmd calldata
let errCodes = fromMaybe defaultPanicCodes cmd.assertions
cores <- unsafeInto <$> getNumProcessors
cores <- liftIO $ unsafeInto <$> getNumProcessors
let solverCount = fromMaybe cores cmd.numSolvers
solver <- getSolver cmd
solver <- liftIO $ getSolver cmd
withSolvers solver solverCount cmd.smttimeout $ \solvers -> do
let opts = VeriOpts { simp = True
, debug = cmd.smtdebug
, maxIter = cmd.maxIterations
, askSmtIters = cmd.askSmtIterations
, loopHeuristic = cmd.loopDetectionHeuristic
, abstRefineConfig = AbstRefineConfig cmd.abstractArithmetic cmd.abstractMemory
, rpcInfo = rpcinfo
}
(expr, res) <- verify solvers opts preState (Just $ checkAssertions errCodes)
case res of
[Qed _] -> do
putStrLn "\nQED: No reachable property violations discovered\n"
liftIO $ putStrLn "\nQED: No reachable property violations discovered\n"
showExtras solvers cmd calldata expr
_ -> do
let cexs = snd <$> mapMaybe getCex res
Expand All @@ -330,58 +340,59 @@ assert cmd = do
, "Could not determine reachability of the following end states:"
, ""
] <> fmap (formatExpr) timeouts
T.putStrLn $ T.unlines (counterexamples <> unknowns)
liftIO $ T.putStrLn $ T.unlines (counterexamples <> unknowns)
showExtras solvers cmd calldata expr
exitFailure
liftIO $ exitFailure

showExtras :: SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> IO ()
showExtras :: App m => SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> m ()
showExtras solvers cmd calldata expr = do
when cmd.showTree $ do
when cmd.showTree $ liftIO $ do
putStrLn "=== Expression ===\n"
T.putStrLn $ formatExpr expr
putStrLn ""
when cmd.showReachableTree $ do
reached <- reachable solvers expr
putStrLn "=== Reachable Expression ===\n"
T.putStrLn (formatExpr . snd $ reached)
putStrLn ""
liftIO $ do
putStrLn "=== Reachable Expression ===\n"
T.putStrLn (formatExpr . snd $ reached)
putStrLn ""
when cmd.getModels $ do
putStrLn $ "=== Models for " <> show (Expr.numBranches expr) <> " branches ===\n"
liftIO $ putStrLn $ "=== Models for " <> show (Expr.numBranches expr) <> " branches ===\n"
ms <- produceModels solvers expr
forM_ ms (showModel (fst calldata))
liftIO $ forM_ ms (showModel (fst calldata))

isTestOrLib :: Text -> Bool
isTestOrLib file = T.isSuffixOf ".t.sol" file || areAnyPrefixOf ["src/test/", "src/tests/", "lib/"] file

areAnyPrefixOf :: [Text] -> Text -> Bool
areAnyPrefixOf prefixes t = any (flip T.isPrefixOf t) prefixes

launchExec :: Command Options.Unwrapped -> IO ()
launchExec :: App m => Command Options.Unwrapped -> m ()
launchExec cmd = do
dapp <- getSrcInfo cmd
vm <- vmFromCommand cmd
dapp <- liftIO $ getSrcInfo cmd
vm <- liftIO $ vmFromCommand cmd
let
block = maybe Fetch.Latest Fetch.BlockNumber cmd.block
rpcinfo = (,) block <$> cmd.rpc

-- TODO: we shouldn't need solvers to execute this code
withSolvers Z3 0 Nothing $ \solvers -> do
vm' <- EVM.Stepper.interpret (Fetch.oracle solvers rpcinfo) vm EVM.Stepper.runFully
when cmd.trace $ T.hPutStr stderr (showTraceTree dapp vm')
writeTraceDapp dapp vm'
case vm'.result of
Just (VMFailure (Revert msg)) -> do
Just (VMFailure (Revert msg)) -> liftIO $ do
let res = case msg of
ConcreteBuf bs -> bs
_ -> "<symbolic>"
putStrLn $ "Revert: " <> (show $ ByteStringS res)
exitWith (ExitFailure 2)
Just (VMFailure err) -> do
Just (VMFailure err) -> liftIO $ do
putStrLn $ "Error: " <> show err
exitWith (ExitFailure 2)
Just (Unfinished p) -> do
Just (Unfinished p) -> liftIO $ do
putStrLn $ "Could not continue execution: " <> show p
exitWith (ExitFailure 2)
Just (VMSuccess buf) -> do
Just (VMSuccess buf) -> liftIO $ do
let msg = case buf of
ConcreteBuf msg' -> msg'
_ -> "<symbolic>"
Expand Down Expand Up @@ -585,7 +596,6 @@ unitTestOptions cmd solvers buildOutput = do
Nothing -> Nothing
, maxIter = cmd.maxIterations
, askSmtIters = cmd.askSmtIterations
, smtDebug = cmd.smtdebug
, smtTimeout = cmd.smttimeout
, solver = cmd.solver
, verbose = cmd.verbose
Expand Down
14 changes: 10 additions & 4 deletions hevm.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ library
EVM.Types,
EVM.UnitTest,
EVM.Sign,
EVM.Effects,
other-modules:
Paths_hevm
autogen-modules:
Expand Down Expand Up @@ -168,7 +169,8 @@ library
stm >= 2.5.0 && < 2.6.0,
spawn >= 0.3 && < 0.4,
filepattern >= 0.1.2 && < 0.2,
witch >= 1.1 && < 1.3
witch >= 1.1 && < 1.3,
unliftio-core >= 0.2.1.0
if !os(windows)
build-depends:
brick >= 1.4 && < 2.0,
Expand Down Expand Up @@ -224,7 +226,8 @@ executable hevm
spawn,
optics-core,
githash >= 0.1.6 && < 0.2,
witch
witch,
unliftio-core
if os(windows)
buildable: False

Expand Down Expand Up @@ -277,7 +280,9 @@ common test-base
operational,
optics-core,
optics-extra,
witch
witch,
unliftio-core,
exceptions

library test-utils
import:
Expand Down Expand Up @@ -370,4 +375,5 @@ benchmark bench
filemanip,
filepath,
containers,
mtl
mtl,
unliftio-core
Loading
Loading