From 14da0b78e9fc0d7304132074e818575c22c24ab9 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 9 Oct 2023 14:13:21 +0200 Subject: [PATCH 01/28] The SMT2 file ought to be printed while executing it --- src/EVM/Fetch.hs | 4 ++-- src/EVM/Solvers.hs | 23 +++++++++++++++++------ src/EVM/SymExec.hs | 27 ++++++++++----------------- test/test.hs | 7 ++++--- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/EVM/Fetch.hs b/src/EVM/Fetch.hs index 4f61a3560..431a14de8 100644 --- a/src/EVM/Fetch.hs +++ b/src/EVM/Fetch.hs @@ -238,13 +238,13 @@ type Fetcher s = Query s -> IO (EVM s ()) -- will be pruned anyway. checkBranch :: SolverGroup -> Prop -> Prop -> IO BranchCondition checkBranch solvers branchcondition pathconditions = do - checkSat solvers (assertProps abstRefineDefault [(branchcondition .&& pathconditions)]) >>= \case + checkSat solvers (assertProps abstRefineDefault [(branchcondition .&& pathconditions)]) Nothing >>= \case -- the condition is unsatisfiable Unsat -> -- if pathconditions are consistent then the condition must be false pure $ Case False -- Sat means its possible for condition to hold Sat _ -> -- is its negation also possible? - checkSat solvers (assertProps abstRefineDefault [(pathconditions .&& (PNeg branchcondition))]) >>= \case + checkSat solvers (assertProps abstRefineDefault [(pathconditions .&& (PNeg branchcondition))]) Nothing >>= \case -- No. The condition must hold Unsat -> pure $ Case True -- Yes. Both branches possible diff --git a/src/EVM/Solvers.hs b/src/EVM/Solvers.hs index 310f1743f..e78c95b6e 100644 --- a/src/EVM/Solvers.hs +++ b/src/EVM/Solvers.hs @@ -15,7 +15,7 @@ import Control.Monad.State.Strict import Data.Char (isSpace) import Data.Map (Map) import Data.Map qualified as Map -import Data.Maybe (fromMaybe) +import Data.Maybe (fromMaybe, isJust, fromJust) import Data.Text qualified as TS import Data.Text.Lazy (Text) import Data.Text.Lazy qualified as T @@ -57,6 +57,7 @@ newtype SolverGroup = SolverGroup (Chan Task) data Task = Task { script :: SMT2 , resultChan :: Chan CheckSatResult + , debugFName :: Maybe String } -- | The result of a call to (check-sat) @@ -79,15 +80,21 @@ isUnsat :: CheckSatResult -> Bool isUnsat Unsat = True isUnsat _ = False -checkSat :: SolverGroup -> SMT2 -> IO CheckSatResult -checkSat (SolverGroup taskQueue) script = do +checkSat :: SolverGroup -> SMT2 -> Maybe String -> IO CheckSatResult +checkSat (SolverGroup taskQueue) script debugFName = do -- prepare result channel resChan <- newChan -- send task to solver group - writeChan taskQueue (Task script resChan) + writeChan taskQueue (Task script resChan debugFName) -- collect result readChan resChan +writeSMT2File :: SMT2 -> Maybe String -> String -> IO () +writeSMT2File smt2 fname abst = + when (isJust fname) $ + do T.writeFile (fromJust fname <> "-" <> abst <> ".smt2") + ("; " <> formatSMT2 smt2 <> "\n\n(check-sat)") + withSolvers :: Solver -> Natural -> Maybe Natural -> (SolverGroup -> IO a) -> IO a withSolvers solver count timeout cont = do -- spawn solvers @@ -106,13 +113,15 @@ withSolvers solver count timeout cont = do killThread orchestrateId pure res where + orchestrate :: Chan Task -> Chan SolverInstance -> IO b orchestrate queue avail = do task <- readChan queue inst <- readChan avail _ <- forkIO $ runTask task inst avail orchestrate queue avail - runTask (Task (SMT2 cmds (RefinementEqs refineEqs) cexvars) r) inst availableInstances = do + runTask (Task smt2@(SMT2 cmds (RefinementEqs refineEqs) cexvars) r debugFName) inst availableInstances = do + writeSMT2File smt2 debugFName ("abstracted") -- reset solver and send all lines of provided script out <- sendScript inst (SMT2 ("(reset)" : cmds) mempty mempty) case out of @@ -128,7 +137,9 @@ withSolvers solver count timeout cont = do "unknown" -> pure Unknown "sat" -> if null refineEqs then Sat <$> getModel inst cexvars else do - _ <- sendScript inst (SMT2 refineEqs mempty mempty) + let refinedSMT2 = SMT2 refineEqs mempty mempty + writeSMT2File refinedSMT2 debugFName ("refined") + _ <- sendScript inst refinedSMT2 sat2 <- sendLine inst "(check-sat)" case sat2 of "unsat" -> pure Unsat diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs index 4203ad509..30ad4938d 100644 --- a/src/EVM/SymExec.hs +++ b/src/EVM/SymExec.hs @@ -24,8 +24,6 @@ import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO qualified as T -import Data.Text.Lazy qualified as TL -import Data.Text.Lazy.IO qualified as TL import Data.Tree.Zipper qualified as Zipper import Data.Tuple (swap) import EVM (makeVm, abstractContract, initialContract, getCodeLocation, isValidJumpDest) @@ -513,7 +511,7 @@ reachable solvers e = do pure (fst tres <> fst fres, subexpr) leaf -> do let query = assertProps abstRefineDefault pcs - res <- checkSat solvers query + res <- checkSat solvers query Nothing case res of Sat _ -> pure ([query], Just leaf) Unsat -> pure ([query], Nothing) @@ -579,18 +577,14 @@ verify solvers opts preState maybepost = do assumes = preState.constraints withQueries = canViolate <&> \leaf -> (assertProps opts.abstRefineConfig (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf) + debugFName = if opts.debug then Just ("verify-query") else Nothing putStrLn $ "Checking for reachability of " <> show (length withQueries) <> " potential property violation(s)" - when opts.debug $ forM_ (zip [(1 :: Int)..] withQueries) $ \(idx, (q, leaf)) -> do - TL.writeFile - ("query-" <> show idx <> ".smt2") - ("; " <> (TL.pack $ show leaf) <> "\n\n" <> formatSMT2 q <> "\n\n(check-sat)") - -- Dispatch the remaining branches to the solver to check for violations results <- flip mapConcurrently withQueries $ \(query, leaf) -> do - res <- checkSat solvers query + res <- checkSat solvers query debugFName pure (res, leaf) let cexs = filter (\(res, _) -> not . isUnsat $ res) results pure $ if Prelude.null cexs then (expr, [Qed ()]) else (expr, fmap toVRes cexs) @@ -690,15 +684,14 @@ equivalenceCheck' solvers branchesA branchesB opts = do -- used or not check :: UnsatCache -> (Set Prop) -> Int -> IO (EquivResult, Bool) check knownUnsat props idx = do - let smt = assertProps opts.abstRefineConfig (Set.toList props) - -- if debug is on, write the query to a file - let filename = "equiv-query-" <> show idx <> ".smt2" - when opts.debug $ TL.writeFile filename (formatSMT2 smt <> "\n\n(check-sat)") - + let + smt = assertProps opts.abstRefineConfig (Set.toList props) + debugFName = if opts.debug then Just "equiv-query" + else Nothing ku <- readTVarIO knownUnsat res <- if subsetAny props ku then pure (True, Unsat) - else (fmap ((False),) (checkSat solvers smt)) + else (fmap ((False),) (checkSat solvers smt debugFName)) case res of (_, Sat x) -> pure (Cex x, False) (quick, Unsat) -> @@ -710,7 +703,7 @@ equivalenceCheck' solvers branchesA branchesB opts = do atomically $ readTVar knownUnsat >>= writeTVar knownUnsat . (props :) pure (Qed (), False) (_, EVM.Solvers.Unknown) -> pure (Timeout (), False) - (_, Error txt) -> internalError $ "solver returned: " <> (T.unpack txt) <> if opts.debug then "\n SMT file was: " <> filename <> "" else "" + (_, Error txt) -> internalError $ "solver returned: " <> (T.unpack txt) -- Allows us to run it in parallel. Note that this (seems to) run it -- from left-to-right, and with a max of K threads. This is in contrast to @@ -791,7 +784,7 @@ produceModels solvers expr = do let flattened = flattenExpr expr withQueries = fmap (\e -> ((assertProps abstRefineDefault) . extractProps $ e, e)) flattened results <- flip mapConcurrently withQueries $ \(query, leaf) -> do - res <- checkSat solvers query + res <- checkSat solvers query Nothing pure (res, leaf) pure $ fmap swap $ filter (\(res, _) -> not . isUnsat $ res) results diff --git a/test/test.hs b/test/test.hs index c40d314c1..190ec1946 100644 --- a/test/test.hs +++ b/test/test.hs @@ -1807,7 +1807,7 @@ tests = testGroup "hevm" } } |] - (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint16)" [AbiUIntType 16])) [] defaultVeriOpts + (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint16)" [AbiUIntType 16])) [] debugVeriOpts putStrLn "DIV by zero is zero" , -- Somewhat tautological since we are asserting the precondition @@ -2036,7 +2036,7 @@ tests = testGroup "hevm" } } |] - (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts + (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] debugVeriOpts let ints = map (flip getVar "arg1") [a,b] assertBool "0 must be one of the Cex-es" $ isJust $ List.elemIndex 0 ints putStrLn "expected 2 counterexamples found, one Cex is the 0 value" @@ -3130,7 +3130,8 @@ checkEquivBase mkprop l r = withSolvers Z3 1 (Just 1) $ \solvers -> do pure True else do let smt = assertPropsNoSimp abstRefineDefault [mkprop l r] - res <- checkSat solvers smt + let smt = assertProps abstRefineDefault [mkprop l r] + res <- checkSat solvers smt Nothing print res pure $ case res of Unsat -> True From 512d6c1a1d999ced1b2a777cdc44d5a8f16cf69a Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 9 Oct 2023 14:15:12 +0200 Subject: [PATCH 02/28] Adding Monadic config --- cli/cli.hs | 75 ++- hevm.cabal | 11 +- src/EVM/Effects.hs | 38 ++ src/EVM/Fetch.hs | 25 +- src/EVM/Format.hs | 1 + src/EVM/SMT.hs | 61 +- src/EVM/Solidity.hs | 7 +- src/EVM/Solvers.hs | 138 ++--- src/EVM/Stepper.hs | 20 +- src/EVM/SymExec.hs | 199 ++++--- src/EVM/UnitTest.hs | 28 +- test/BlockchainTests.hs | 8 +- test/EVM/Test/BlockchainTests.hs | 81 ++- test/EVM/Test/Tracing.hs | 104 ++-- test/EVM/Test/Utils.hs | 15 +- test/test.hs | 966 ++++++++++++++++--------------- 16 files changed, 993 insertions(+), 784 deletions(-) create mode 100644 src/EVM/Effects.hs diff --git a/cli/cli.hs b/cli/cli.hs index d5bed0eac..2f40e286a 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -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) @@ -41,8 +42,9 @@ 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. @@ -183,6 +185,9 @@ getFullVersion = showVersion Paths.version <> " [" <> gitVersion <> "]" Right val -> "git rev " <> giBranch val <> "@" <> giHash val Left _ -> "no git revision present" +mainEnv :: Env +mainEnv = Env { config = Config { dumpQueries = True } } + main :: IO () main = do cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator" @@ -190,28 +195,29 @@ main = do Version {} ->putStrLn getFullVersion Symbolic {} -> do root <- getRoot cmd - withCurrentDirectory root $ assert cmd - Equivalence {} -> equivalence cmd - Exec {} -> - launchExec cmd + withCurrentDirectory root $ runEnv mainEnv $ assert cmd + Equivalence {} -> runEnv mainEnv $ equivalence cmd + Exec {} -> runEnv mainEnv $ 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 mainEnv $ 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 + :: (MonadUnliftIO m, ReadConfig m) + => Command Options.Unwrapped -> m () equivalence cmd = do let bytecodeA = hexByteString "--code" . strip0x $ cmd.codeA bytecodeB = hexByteString "--code" . strip0x $ cmd.codeB @@ -223,17 +229,17 @@ equivalence cmd = do , 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:" @@ -289,16 +295,18 @@ 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 + :: (MonadUnliftIO m, ReadConfig 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 @@ -310,7 +318,7 @@ assert cmd = do } (expr, res) <- verify solvers opts preState (Just $ checkAssertions errCodes) case res of - [Qed _] -> do + [Qed _] -> liftIO $ do putStrLn "\nQED: No reachable property violations discovered\n" showExtras solvers cmd calldata expr _ -> do @@ -330,9 +338,10 @@ assert cmd = do , "Could not determine reachability of the following end states:" , "" ] <> fmap (formatExpr) timeouts - T.putStrLn $ T.unlines (counterexamples <> unknowns) - showExtras solvers cmd calldata expr - exitFailure + liftIO $ do + T.putStrLn $ T.unlines (counterexamples <> unknowns) + showExtras solvers cmd calldata expr + exitFailure showExtras :: SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> IO () showExtras solvers cmd calldata expr = do @@ -356,10 +365,12 @@ isTestOrLib file = T.isSuffixOf ".t.sol" file || areAnyPrefixOf ["src/test/", "s areAnyPrefixOf :: [Text] -> Text -> Bool areAnyPrefixOf prefixes t = any (flip T.isPrefixOf t) prefixes -launchExec :: Command Options.Unwrapped -> IO () +launchExec + :: (MonadUnliftIO m, ReadConfig 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 @@ -367,21 +378,21 @@ launchExec cmd = do -- 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') + liftIO $ when cmd.trace $ T.hPutStr stderr (showTraceTree 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 _ -> "" 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' _ -> "" diff --git a/hevm.cabal b/hevm.cabal index ffd4fe7fa..07080988c 100644 --- a/hevm.cabal +++ b/hevm.cabal @@ -99,6 +99,7 @@ library EVM.Types, EVM.UnitTest, EVM.Sign, + EVM.Effects, other-modules: Paths_hevm autogen-modules: @@ -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, @@ -224,7 +226,8 @@ executable hevm spawn, optics-core, githash >= 0.1.6 && < 0.2, - witch + witch, + unliftio-core if os(windows) buildable: False @@ -277,7 +280,9 @@ common test-base operational, optics-core, optics-extra, - witch + witch, + unliftio-core, + exceptions library test-utils import: diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs new file mode 100644 index 000000000..9f37649c0 --- /dev/null +++ b/src/EVM/Effects.hs @@ -0,0 +1,38 @@ +{-# Language RankNTypes #-} +{-# Language FlexibleInstances #-} +{-# Language KindSignatures #-} +{-# Language DataKinds #-} +{-# Language GADTs #-} +{-# Language DerivingStrategies #-} +{-# Language DuplicateRecordFields #-} +{-# Language NoFieldSelectors #-} + +module EVM.Effects where + +import Control.Monad.Reader + +-- Abstract Effects -------------------------------------------------------------------------------- +-- Here we define the abstract interface for the effects that we wish to model + +-- This is a concrete datatype that contains handlers for the above effects inside the IO monad. +data Config = Config + { + dumpQueries :: Bool + } + + +data Env = Env + { config :: Config + } + +-- forall {r} {m :: Type -> Type} {a}. r -> ReaderT r m a -> m a +runEnv :: Env -> ReaderT Env m a -> m a +runEnv e a = runReaderT a e + +class Monad m => ReadConfig m where + readConfig :: m Config + +instance Monad m => ReadConfig (ReaderT Env m) where + readConfig = do + e <- ask + pure e.config diff --git a/src/EVM/Fetch.hs b/src/EVM/Fetch.hs index 431a14de8..109e88990 100644 --- a/src/EVM/Fetch.hs +++ b/src/EVM/Fetch.hs @@ -27,6 +27,9 @@ import Network.Wreq.Session (Session) import Network.Wreq.Session qualified as Session import Numeric.Natural (Natural) import System.Process +import Control.Monad.IO.Class +import EVM.Effects +import Control.Monad.IO.Unlift -- | Abstract representation of an RPC fetch request data RpcQuery a where @@ -182,23 +185,23 @@ fetchChainIdFrom url = do sess <- Session.newAPISession fetchQuery Latest (fetchWithSession url sess) QueryChainId -http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher s +http :: Natural -> Maybe Natural -> BlockNumber -> Text -> Fetcher m s http smtjobs smttimeout n url q = withSolvers Z3 smtjobs smttimeout $ \s -> oracle s (Just (n, url)) q -zero :: Natural -> Maybe Natural -> Fetcher s +zero :: Natural -> Maybe Natural -> Fetcher m s zero smtjobs smttimeout q = withSolvers Z3 smtjobs smttimeout $ \s -> oracle s Nothing q -- smtsolving + (http or zero) -oracle :: SolverGroup -> RpcInfo -> Fetcher s +oracle :: SolverGroup -> RpcInfo -> Fetcher m s oracle solvers info q = do case q of PleaseDoFFI vals continue -> case vals of cmd : args -> do - (_, stdout', _) <- readProcessWithExitCode cmd args "" + (_, stdout', _) <- liftIO $ readProcessWithExitCode cmd args "" pure . continue . encodeAbiValue $ AbiTuple (RegularVector.fromList [ AbiBytesDynamic . hexText . pack $ stdout']) _ -> internalError (show vals) @@ -215,7 +218,7 @@ oracle solvers info q = do AbstractBase -> unknownContract (LitAddr addr) EmptyBase -> emptyContract in pure $ Just c - Just (n, url) -> fetchContractFrom n url addr + Just (n, url) -> liftIO $ fetchContractFrom n url addr case contract of Just x -> pure $ continue x Nothing -> internalError $ "oracle error: " ++ show q @@ -224,27 +227,27 @@ oracle solvers info q = do case info of Nothing -> pure (continue 0) Just (n, url) -> - fetchSlotFrom n url addr slot >>= \case + liftIO $ fetchSlotFrom n url addr slot >>= \case Just x -> pure (continue x) Nothing -> internalError $ "oracle error: " ++ show q -type Fetcher s = Query s -> IO (EVM s ()) +type Fetcher m s = (ReadConfig m, MonadUnliftIO m) => Query s -> m (EVM s ()) -- | Checks which branches are satisfiable, checking the pathconditions for consistency -- if the third argument is true. -- When in debug mode, we do not want to be able to navigate to dead paths, -- but for normal execution paths with inconsistent pathconditions -- will be pruned anyway. -checkBranch :: SolverGroup -> Prop -> Prop -> IO BranchCondition -checkBranch solvers branchcondition pathconditions = do - checkSat solvers (assertProps abstRefineDefault [(branchcondition .&& pathconditions)]) Nothing >>= \case +checkBranch :: (MonadUnliftIO m, ReadConfig m) => SolverGroup -> Prop -> Prop -> m BranchCondition +checkBranch solvers branchcondition pathconditions = liftIO $ do + checkSat solvers (assertProps abstRefineDefault [(branchcondition .&& pathconditions)]) >>= \case -- the condition is unsatisfiable Unsat -> -- if pathconditions are consistent then the condition must be false pure $ Case False -- Sat means its possible for condition to hold Sat _ -> -- is its negation also possible? - checkSat solvers (assertProps abstRefineDefault [(pathconditions .&& (PNeg branchcondition))]) Nothing >>= \case + checkSat solvers (assertProps abstRefineDefault [(pathconditions .&& (PNeg branchcondition))]) >>= \case -- No. The condition must hold Unsat -> pure $ Case True -- Yes. Both branches possible diff --git a/src/EVM/Format.hs b/src/EVM/Format.hs index 7a0b9c1c4..b5561bed4 100644 --- a/src/EVM/Format.hs +++ b/src/EVM/Format.hs @@ -5,6 +5,7 @@ module EVM.Format ( formatExpr , formatSomeExpr , formatPartial + , formatProp , contractNamePart , contractPathPart , showError diff --git a/src/EVM/SMT.hs b/src/EVM/SMT.hs index b199031e2..2e5d46e7a 100644 --- a/src/EVM/SMT.hs +++ b/src/EVM/SMT.hs @@ -32,6 +32,7 @@ import Language.SMT2.Syntax (Symbol, SpecConstant(..), GeneralRes(..), Term(..), import Numeric (readHex, readBin) import Witch (into, unsafeInto) import Control.Monad.State +import EVM.Format (formatProp) import EVM.CSE import EVM.Expr (writeByte, bufLengthEnv, containsNode, bufLength, minLength, inRange) @@ -103,8 +104,14 @@ data SMTCex = SMTCex -- | Used for abstraction-refinement of the SMT formula. Contains assertions that make our query fully precise. These will be added to the assertion stack if we get `sat` with the abstracted query. -newtype RefinementEqs = RefinementEqs [Builder] - deriving (Eq, Show, Monoid, Semigroup) +data RefinementEqs = RefinementEqs [Builder] [Prop] + deriving (Eq, Show) + +instance Semigroup RefinementEqs where + (RefinementEqs a b) <> (RefinementEqs a2 b2) = RefinementEqs (a <> a2) (b <> b2) + +instance Monoid RefinementEqs where + mempty = RefinementEqs mempty mempty flattenBufs :: SMTCex -> Maybe SMTCex flattenBufs cex = do @@ -125,17 +132,20 @@ collapse model = case toBuf model of getVar :: SMTCex -> TS.Text -> W256 getVar cex name = fromJust $ Map.lookup (Var name) cex.vars -data SMT2 = SMT2 [Builder] RefinementEqs CexVars +data SMT2 = SMT2 [Builder] RefinementEqs CexVars [Prop] deriving (Eq, Show) instance Semigroup SMT2 where - (SMT2 a (RefinementEqs b) c) <> (SMT2 a2 (RefinementEqs b2) c2) = SMT2 (a <> a2) (RefinementEqs (b <> b2)) (c <> c2) + (SMT2 a (RefinementEqs b refps) c d) <> (SMT2 a2 (RefinementEqs b2 refps2) c2 d2) = SMT2 (a <> a2) (RefinementEqs (b <> b2) (refps <> refps2)) (c <> c2) (d <> d2) instance Monoid SMT2 where - mempty = SMT2 mempty mempty mempty + mempty = SMT2 mempty mempty mempty mempty formatSMT2 :: SMT2 -> Text -formatSMT2 (SMT2 ls _ _) = T.unlines (fmap toLazyText ls) +formatSMT2 (SMT2 ls _ _ ps) = expr <> smt2 + where + expr = T.concat [T.singleton ';', T.replace "\n" "\n;" $ T.pack . TS.unpack $ TS.unlines (fmap formatProp ps)] + smt2 = T.unlines (fmap toLazyText ls) -- | Reads all intermediate variables from the builder state and produces SMT declaring them as constants declareIntermediates :: BufEnv -> StoreEnv -> SMT2 @@ -144,16 +154,16 @@ declareIntermediates bufs stores = encBs = Map.mapWithKey encodeBuf bufs sorted = List.sortBy compareFst $ Map.toList $ encSs <> encBs decls = fmap snd sorted - smt2 = (SMT2 [fromText "; intermediate buffers & stores"] mempty mempty):decls - in foldr (<>) (SMT2[""] mempty mempty) smt2 + smt2 = (SMT2 [fromText "; intermediate buffers & stores"] mempty mempty mempty):decls + in foldr (<>) (SMT2[""] mempty mempty mempty) smt2 where compareFst (l, _) (r, _) = compare l r encodeBuf n expr = - SMT2 ["(define-const buf" <> (fromString . show $ n) <> " Buf " <> exprToSMT expr <> ")\n"] mempty mempty <> encodeBufLen n expr + SMT2 ["(define-const buf" <> (fromString . show $ n) <> " Buf " <> exprToSMT expr <> ")\n"] mempty mempty mempty <> encodeBufLen n expr encodeBufLen n expr = - SMT2 ["(define-const buf" <> (fromString . show $ n) <>"_length" <> " (_ BitVec 256) " <> exprToSMT (bufLengthEnv bufs True expr) <> ")"] mempty mempty + SMT2 ["(define-const buf" <> (fromString . show $ n) <>"_length" <> " (_ BitVec 256) " <> exprToSMT (bufLengthEnv bufs True expr) <> ")"] mempty mempty mempty encodeStore n expr = - SMT2 ["(define-const store" <> (fromString . show $ n) <> " Storage " <> exprToSMT expr <> ")"] mempty mempty + SMT2 ["(define-const store" <> (fromString . show $ n) <> " Storage " <> exprToSMT expr <> ")"] mempty mempty mempty data AbstState = AbstState { words :: Map (Expr EWord) Int @@ -190,7 +200,7 @@ abstractAwayProps abstRefineConfig ps = runState (mapM abstrAway ps) (AbstState pure $ Var (TS.pack ("abst_" ++ show next)) smt2Line :: Builder -> SMT2 -smt2Line txt = SMT2 [txt] mempty mempty +smt2Line txt = SMT2 [txt] mempty mempty mempty assertProps :: AbstRefineConfig -> [Prop] -> SMT2 assertProps conf ps = assertPropsNoSimp conf (Expr.simplifyProps ps) @@ -221,9 +231,10 @@ assertPropsNoSimp abstRefineConfig ps = <> keccakAssumes <> readAssumes <> smt2Line "" - <> SMT2 (fmap (\p -> "(assert " <> p <> ")") encs) mempty mempty - <> SMT2 mempty (RefinementEqs $ fmap (\p -> "(assert " <> p <> ")") abstSMT) mempty - <> SMT2 mempty mempty mempty { storeReads = storageReads } + <> SMT2 (fmap (\p -> "(assert " <> p <> ")") encs) mempty mempty mempty + <> SMT2 mempty (RefinementEqs (fmap (\p -> "(assert " <> p <> ")") abstSMT) (psElimAbst <> abstProps)) mempty mempty + <> SMT2 mempty mempty mempty { storeReads = storageReads } mempty + <> SMT2 mempty mempty mempty ps where (psElim, bufs, stores) = eliminateProps ps @@ -251,13 +262,13 @@ assertPropsNoSimp abstRefineConfig ps = keccakAssumes = smt2Line "; keccak assumptions" - <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakAssumptions psElim bufVals storeVals)) mempty mempty + <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakAssumptions psElim bufVals storeVals)) mempty mempty mempty <> smt2Line "; keccak computations" - <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakCompute psElim bufVals storeVals)) mempty mempty + <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (keccakCompute psElim bufVals storeVals)) mempty mempty mempty readAssumes = smt2Line "; read assumptions" - <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (assertReads psElim bufs stores)) mempty mempty + <> SMT2 (fmap (\p -> "(assert " <> propToSMT p <> ")") (assertReads psElim bufs stores)) mempty mempty mempty referencedAbstractStores :: TraversableTerm a => a -> Set Builder referencedAbstractStores term = foldTerm go mempty term @@ -395,7 +406,7 @@ discoverMaxReads props benv senv = bufMap -- | Returns an SMT2 object with all buffers referenced from the input props declared, and with the appropriate cex extraction metadata attached declareBufs :: [Prop] -> BufEnv -> StoreEnv -> SMT2 -declareBufs props bufEnv storeEnv = SMT2 ("; buffers" : fmap declareBuf allBufs <> ("; buffer lengths" : fmap declareLength allBufs)) mempty cexvars +declareBufs props bufEnv storeEnv = SMT2 ("; buffers" : fmap declareBuf allBufs <> ("; buffer lengths" : fmap declareLength allBufs)) mempty cexvars mempty where cexvars = (mempty :: CexVars){ buffers = discoverMaxReads props bufEnv storeEnv } allBufs = fmap fromLazyText $ Map.keys cexvars.buffers @@ -404,39 +415,39 @@ declareBufs props bufEnv storeEnv = SMT2 ("; buffers" : fmap declareBuf allBufs -- Given a list of variable names, create an SMT2 object with the variables declared declareVars :: [Builder] -> SMT2 -declareVars names = SMT2 (["; variables"] <> fmap declare names) mempty cexvars +declareVars names = SMT2 (["; variables"] <> fmap declare names) mempty cexvars mempty where declare n = "(declare-const " <> n <> " (_ BitVec 256))" cexvars = (mempty :: CexVars){ calldata = fmap toLazyText names } -- Given a list of variable names, create an SMT2 object with the variables declared declareAddrs :: [Builder] -> SMT2 -declareAddrs names = SMT2 (["; symbolic addresseses"] <> fmap declare names) mempty cexvars +declareAddrs names = SMT2 (["; symbolic addresseses"] <> fmap declare names) mempty cexvars mempty where declare n = "(declare-const " <> n <> " Addr)" cexvars = (mempty :: CexVars){ addrs = fmap toLazyText names } declareFrameContext :: [(Builder, [Prop])] -> SMT2 -declareFrameContext names = SMT2 (["; frame context"] <> concatMap declare names) mempty cexvars +declareFrameContext names = SMT2 (["; frame context"] <> concatMap declare names) mempty cexvars mempty where declare (n,props) = [ "(declare-const " <> n <> " (_ BitVec 256))" ] <> fmap (\p -> "(assert " <> propToSMT p <> ")") props cexvars = (mempty :: CexVars){ txContext = fmap (toLazyText . fst) names } declareAbstractStores :: [Builder] -> SMT2 -declareAbstractStores names = SMT2 (["; abstract base stores"] <> fmap declare names) mempty mempty +declareAbstractStores names = SMT2 (["; abstract base stores"] <> fmap declare names) mempty mempty mempty where declare n = "(declare-const " <> n <> " Storage)" declareBlockContext :: [(Builder, [Prop])] -> SMT2 -declareBlockContext names = SMT2 (["; block context"] <> concatMap declare names) mempty cexvars +declareBlockContext names = SMT2 (["; block context"] <> concatMap declare names) mempty cexvars mempty where declare (n, props) = [ "(declare-const " <> n <> " (_ BitVec 256))" ] <> fmap (\p -> "(assert " <> propToSMT p <> ")") props cexvars = (mempty :: CexVars){ blockContext = fmap (toLazyText . fst) names } prelude :: SMT2 -prelude = SMT2 src mempty mempty +prelude = SMT2 src mempty mempty mempty where src = fmap (fromLazyText . T.drop 2) . T.lines $ [i| ; logic diff --git a/src/EVM/Solidity.hs b/src/EVM/Solidity.hs index 57fd6107e..f07686ba9 100644 --- a/src/EVM/Solidity.hs +++ b/src/EVM/Solidity.hs @@ -51,6 +51,7 @@ import Optics.Operators.Unsafe import Control.Applicative import Control.Monad +import Control.Monad.IO.Unlift import Data.Aeson hiding (json) import Data.Aeson.Types import Data.Aeson.Optics @@ -378,8 +379,10 @@ solidity contract src = do let (Contracts sol, _, _) = fromJust $ readStdJSON json pure $ Map.lookup (path <> ":" <> contract) sol <&> (.creationCode) -solcRuntime :: Text -> Text -> IO (Maybe ByteString) -solcRuntime contract src = do +solcRuntime + :: (MonadUnliftIO m) + => Text -> Text -> m (Maybe ByteString) +solcRuntime contract src = liftIO $ do (json, path) <- solidity' src case readStdJSON json of Just (Contracts sol, _, _) -> pure $ Map.lookup (path <> ":" <> contract) sol <&> (.runtimeCode) diff --git a/src/EVM/Solvers.hs b/src/EVM/Solvers.hs index e78c95b6e..f802b76ff 100644 --- a/src/EVM/Solvers.hs +++ b/src/EVM/Solvers.hs @@ -12,10 +12,11 @@ import Control.Concurrent.Chan (Chan, newChan, writeChan, readChan) import Control.Concurrent (forkIO, killThread) import Control.Monad import Control.Monad.State.Strict +import Control.Monad.IO.Unlift import Data.Char (isSpace) import Data.Map (Map) import Data.Map qualified as Map -import Data.Maybe (fromMaybe, isJust, fromJust) +import Data.Maybe (fromMaybe) import Data.Text qualified as TS import Data.Text.Lazy (Text) import Data.Text.Lazy qualified as T @@ -23,6 +24,7 @@ import Data.Text.Lazy.IO qualified as T import Data.Text.Lazy.Builder import System.Process (createProcess, cleanupProcess, proc, ProcessHandle, std_in, std_out, std_err, StdStream(..)) import Witch (into) +import EVM.Effects import EVM.SMT import EVM.Types (W256, Expr(AbstractBuf), internalError) @@ -57,7 +59,6 @@ newtype SolverGroup = SolverGroup (Chan Task) data Task = Task { script :: SMT2 , resultChan :: Chan CheckSatResult - , debugFName :: Maybe String } -- | The result of a call to (check-sat) @@ -80,78 +81,83 @@ isUnsat :: CheckSatResult -> Bool isUnsat Unsat = True isUnsat _ = False -checkSat :: SolverGroup -> SMT2 -> Maybe String -> IO CheckSatResult -checkSat (SolverGroup taskQueue) script debugFName = do +checkSat :: SolverGroup -> SMT2 -> IO CheckSatResult +checkSat (SolverGroup taskQueue) script = do -- prepare result channel resChan <- newChan -- send task to solver group - writeChan taskQueue (Task script resChan debugFName) + writeChan taskQueue (Task script resChan) -- collect result readChan resChan -writeSMT2File :: SMT2 -> Maybe String -> String -> IO () -writeSMT2File smt2 fname abst = - when (isJust fname) $ - do T.writeFile (fromJust fname <> "-" <> abst <> ".smt2") - ("; " <> formatSMT2 smt2 <> "\n\n(check-sat)") +writeSMT2File :: SMT2 -> Int -> String -> IO () +writeSMT2File smt2 count abst = + do + let content = formatSMT2 smt2 <> "\n\n(check-sat)" + T.writeFile ("query-" <> (show count) <> "-" <> abst <> ".smt2") content -withSolvers :: Solver -> Natural -> Maybe Natural -> (SolverGroup -> IO a) -> IO a +withSolvers :: (MonadUnliftIO m, ReadConfig m) => Solver -> Natural -> Maybe Natural -> (SolverGroup -> m a) -> m a withSolvers solver count timeout cont = do - -- spawn solvers - instances <- mapM (const $ spawnSolver solver timeout) [1..count] - -- spawn orchestration thread - taskQueue <- newChan - availableInstances <- newChan - forM_ instances (writeChan availableInstances) - orchestrateId <- forkIO $ orchestrate taskQueue availableInstances - - -- run continuation with task queue - res <- cont (SolverGroup taskQueue) - - -- cleanup and return results - mapM_ stopSolver instances - killThread orchestrateId - pure res + -- spawn solvers + instances <- mapM (const $ liftIO $ spawnSolver solver timeout) [1..count] + -- spawn orchestration thread + taskQueue <- liftIO newChan + availableInstances <- liftIO newChan + liftIO $ forM_ instances (writeChan availableInstances) + orchestrate' <- toIO $ orchestrate taskQueue availableInstances 0 + orchestrateId <- liftIO $ forkIO orchestrate' + + -- run continuation with task queue + res <- cont (SolverGroup taskQueue) + + -- cleanup and return results + liftIO $ mapM_ (stopSolver) instances + liftIO $ killThread orchestrateId + pure res where - orchestrate :: Chan Task -> Chan SolverInstance -> IO b - orchestrate queue avail = do - task <- readChan queue - inst <- readChan avail - _ <- forkIO $ runTask task inst avail - orchestrate queue avail - - runTask (Task smt2@(SMT2 cmds (RefinementEqs refineEqs) cexvars) r debugFName) inst availableInstances = do - writeSMT2File smt2 debugFName ("abstracted") - -- reset solver and send all lines of provided script - out <- sendScript inst (SMT2 ("(reset)" : cmds) mempty mempty) - case out of - -- if we got an error then return it - Left e -> writeChan r (Error ("error while writing SMT to solver: " <> T.toStrict e)) - -- otherwise call (check-sat), parse the result, and send it down the result channel - Right () -> do - sat <- sendLine inst "(check-sat)" - res <- do - case sat of - "unsat" -> pure Unsat - "timeout" -> pure Unknown - "unknown" -> pure Unknown - "sat" -> if null refineEqs then Sat <$> getModel inst cexvars - else do - let refinedSMT2 = SMT2 refineEqs mempty mempty - writeSMT2File refinedSMT2 debugFName ("refined") - _ <- sendScript inst refinedSMT2 - sat2 <- sendLine inst "(check-sat)" - case sat2 of - "unsat" -> pure Unsat - "timeout" -> pure Unknown - "unknown" -> pure Unknown - "sat" -> Sat <$> getModel inst cexvars - _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> sat2 - _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> sat - writeChan r res - - -- put the instance back in the list of available instances - writeChan availableInstances inst + orchestrate :: (MonadUnliftIO m, ReadConfig m) => Chan Task -> Chan SolverInstance -> Int -> m b + orchestrate queue avail fileCounter = do + task <- liftIO $ readChan queue + inst <- liftIO $ readChan avail + runTask' <- toIO $ runTask task inst avail fileCounter + _ <- liftIO $ forkIO runTask' + orchestrate queue avail (fileCounter + 1) + + runTask :: (MonadIO m, ReadConfig m) => Task -> SolverInstance -> Chan SolverInstance -> Int -> m () + runTask (Task smt2@(SMT2 cmds (RefinementEqs refineEqs refps) cexvars ps) r) inst availableInstances fileCounter = do + conf <- readConfig + liftIO $ do + when (conf.dumpQueries) $ writeSMT2File smt2 fileCounter "abstracted" + -- reset solver and send all lines of provided script + out <- sendScript inst (SMT2 ("(reset)" : cmds) mempty mempty ps) + case out of + -- if we got an error then return it + Left e -> writeChan r (Error ("error while writing SMT to solver: " <> T.toStrict e)) + -- otherwise call (check-sat), parse the result, and send it down the result channel + Right () -> do + sat <- sendLine inst "(check-sat)" + res <- do + case sat of + "unsat" -> pure Unsat + "timeout" -> pure Unknown + "unknown" -> pure Unknown + "sat" -> if null refineEqs then Sat <$> getModel inst cexvars + else do + let refinedSMT2 = SMT2 refineEqs mempty mempty (ps <> refps) + writeSMT2File refinedSMT2 fileCounter "refined" + _ <- sendScript inst refinedSMT2 + sat2 <- sendLine inst "(check-sat)" + case sat2 of + "unsat" -> pure Unsat + "timeout" -> pure Unknown + "unknown" -> pure Unknown + "sat" -> Sat <$> getModel inst cexvars + _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> sat2 + _ -> pure . Error $ T.toStrict $ "Unable to parse solver output: " <> sat + writeChan r res + + -- put the instance back in the list of available instances + writeChan availableInstances inst getModel :: SolverInstance -> CexVars -> IO SMTCex getModel inst cexvars = do @@ -273,7 +279,7 @@ stopSolver (SolverInstance _ stdin stdout stderr process) = cleanupProcess (Just -- | Sends a list of commands to the solver. Returns the first error, if there was one. sendScript :: SolverInstance -> SMT2 -> IO (Either Text ()) -sendScript solver (SMT2 cmds _ _) = do +sendScript solver (SMT2 cmds _ _ _) = do let sexprs = splitSExpr $ fmap toLazyText cmds go sexprs where diff --git a/src/EVM/Stepper.hs b/src/EVM/Stepper.hs index c305f632b..b942b5839 100644 --- a/src/EVM/Stepper.hs +++ b/src/EVM/Stepper.hs @@ -1,4 +1,7 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} + + module EVM.Stepper ( Action (..) @@ -31,6 +34,9 @@ import EVM.Exec qualified import EVM.Fetch qualified as Fetch import EVM.Types import Control.Monad.ST (stToIO, RealWorld) +import Control.Monad.IO.Class +import EVM.Effects +import Control.Monad.IO.Unlift -- | The instruction type of the operational monad data Action s a where @@ -104,30 +110,30 @@ runFully = do enter :: Text -> Stepper s () enter t = evm (EVM.pushTrace (EntryTrace t)) -interpret :: Fetch.Fetcher RealWorld -> VM RealWorld -> Stepper RealWorld a -> IO a +interpret :: forall m a . (ReadConfig m, MonadUnliftIO m) => Fetch.Fetcher m RealWorld -> VM RealWorld -> Stepper RealWorld a -> m a interpret fetcher vm = eval . view where - eval :: ProgramView (Action RealWorld) a -> IO a + eval :: ProgramView (Action RealWorld) a -> m a eval (Return x) = pure x eval (action :>>= k) = case action of Exec -> do - (r, vm') <- stToIO $ runStateT EVM.Exec.exec vm + (r, vm') <- liftIO $ stToIO $ runStateT EVM.Exec.exec vm interpret fetcher vm' (k r) Wait (PleaseAskSMT (Lit c) _ continue) -> do - (r, vm') <- stToIO $ runStateT (continue (Case (c > 0))) vm + (r, vm') <- liftIO $ stToIO $ runStateT (continue (Case (c > 0))) vm interpret fetcher vm' (k r) Wait (PleaseAskSMT c _ _) -> error $ "cannot handle symbolic branch conditions in this interpreter: " <> show c Wait q -> do m <- fetcher q - vm' <- stToIO $ execStateT m vm + vm' <- liftIO $ stToIO $ execStateT m vm interpret fetcher vm' (k ()) Ask _ -> internalError "cannot make choices with this interpreter" IOAct m -> do - r <- m + r <- liftIO $ m interpret fetcher vm (k r) EVM m -> do - (r, vm') <- stToIO $ runStateT m vm + (r, vm') <- liftIO $ stToIO $ runStateT m vm interpret fetcher vm' (k r) diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs index 30ad4938d..e61895b48 100644 --- a/src/EVM/SymExec.hs +++ b/src/EVM/SymExec.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE ScopedTypeVariables #-} module EVM.SymExec where @@ -46,6 +47,8 @@ import GHC.Generics (Generic) import Optics.Core import Options.Generic (ParseField, ParseFields, ParseRecord) import Witch (into, unsafeInto) +import EVM.Effects +import Control.Monad.IO.Unlift data LoopHeuristic = Naive @@ -239,45 +242,44 @@ loadSymVM x callvalue cd create = -- | Interpreter which explores all paths at branching points. Returns an -- 'Expr End' representing the possible executions. interpret - :: Fetch.Fetcher RealWorld + :: forall m . (MonadUnliftIO m, ReadConfig m) + => Fetch.Fetcher m RealWorld -> Maybe Integer -- max iterations -> Integer -- ask smt iterations -> LoopHeuristic -> VM RealWorld -> Stepper RealWorld (Expr End) - -> IO (Expr End) + -> m (Expr End) interpret fetcher maxIter askSmtIters heuristic vm = eval . Operational.view where eval :: Operational.ProgramView (Stepper.Action RealWorld) (Expr End) - -> IO (Expr End) + -> m (Expr End) eval (Operational.Return x) = pure x eval (action Operational.:>>= k) = case action of Stepper.Exec -> do - (r, vm') <- stToIO $ runStateT exec vm + (r, vm') <- liftIO $ stToIO $ runStateT exec vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) Stepper.IOAct q -> do - r <- q + r <- liftIO q interpret fetcher maxIter askSmtIters heuristic vm (k r) Stepper.Ask (PleaseChoosePath cond continue) -> do - (a, b) <- concurrently - (do - (ra, vma) <- stToIO $ runStateT (continue True) vm { result = Nothing } - interpret fetcher maxIter askSmtIters heuristic vma (k ra) - ) - (do - (rb, vmb) <- stToIO $ runStateT (continue False) vm { result = Nothing } - interpret fetcher maxIter askSmtIters heuristic vmb (k rb) - ) + evalLeft <- toIO $ do + (ra, vma) <- liftIO $ stToIO $ runStateT (continue True) vm { result = Nothing } + interpret fetcher maxIter askSmtIters heuristic vma (k ra) + evalRight <- toIO $ do + (rb, vmb) <- liftIO $ stToIO $ runStateT (continue False) vm { result = Nothing } + interpret fetcher maxIter askSmtIters heuristic vmb (k rb) + (a, b) <- liftIO $ concurrently evalLeft evalRight pure $ ITE cond a b Stepper.Wait q -> do let performQuery = do - m <- liftIO (fetcher q) - (r, vm') <- stToIO $ runStateT m vm + m <- fetcher q + (r, vm') <- liftIO$ stToIO $ runStateT m vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) case q of @@ -295,7 +297,7 @@ interpret fetcher maxIter askSmtIters heuristic vm = pure $ Partial vm.keccakEqs (Traces (Zipper.toForest vm.traces) vm.env.contracts) $ MaxIterationsReached vm.state.pc vm.state.contract -- No. keep executing _ -> do - (r, vm') <- stToIO $ runStateT (continue (Case (c > 0))) vm + (r, vm') <- liftIO $ stToIO $ runStateT (continue (Case (c > 0))) vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) -- the condition is symbolic @@ -306,7 +308,7 @@ interpret fetcher maxIter askSmtIters heuristic vm = (Just True, _, Just n) -> do -- continue execution down the opposite branch than the one that -- got us to this point and return a partial leaf for the other side - (r, vm') <- stToIO $ runStateT (continue (Case $ not n)) vm + (r, vm') <- liftIO $ stToIO $ runStateT (continue (Case $ not n)) vm a <- interpret fetcher maxIter askSmtIters heuristic vm' (k r) pure $ ITE cond a (Partial vm.keccakEqs (Traces (Zipper.toForest vm.traces) vm.env.contracts) (MaxIterationsReached vm.state.pc vm.state.contract)) -- we're in a loop and askSmtIters has been reached @@ -316,14 +318,14 @@ interpret fetcher maxIter askSmtIters heuristic vm = _ -> do (r, vm') <- case simpProps of -- if we can statically determine unsatisfiability then we skip exploring the jump - [PBool False] -> stToIO $ runStateT (continue (Case False)) vm + [PBool False] -> liftIO $ stToIO $ runStateT (continue (Case False)) vm -- otherwise we explore both branches - _ -> stToIO $ runStateT (continue EVM.Types.Unknown) vm + _ -> liftIO $ stToIO $ runStateT (continue EVM.Types.Unknown) vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) _ -> performQuery Stepper.EVM m -> do - (r, vm') <- stToIO $ runStateT m vm + (r, vm') <- liftIO $ stToIO $ runStateT m vm interpret fetcher maxIter askSmtIters heuristic vm' (k r) maxIterationsReached :: VM s -> Maybe Integer -> Maybe Bool @@ -364,25 +366,27 @@ type Precondition s = VM s -> Prop type Postcondition s = VM s -> Expr End -> Prop checkAssert - :: SolverGroup + :: (MonadUnliftIO m, ReadConfig m) + => SolverGroup -> [Word256] -> ByteString -> Maybe Sig -> [String] -> VeriOpts - -> IO (Expr End, [VerifyResult]) + -> m (Expr End, [VerifyResult]) checkAssert solvers errs c signature' concreteArgs opts = verifyContract solvers c signature' concreteArgs opts Nothing (Just $ checkAssertions errs) getExpr - :: SolverGroup + :: (MonadUnliftIO m, ReadConfig m) + => SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts - -> IO (Expr End) + -> m (Expr End) getExpr solvers c signature' concreteArgs opts = do - preState <- stToIO $ abstractVM (mkCalldata signature' concreteArgs) c Nothing False + preState <- liftIO $ stToIO $ abstractVM (mkCalldata signature' concreteArgs) c Nothing False exprInter <- interpret (Fetch.oracle solvers opts.rpcInfo) opts.maxIter opts.askSmtIters opts.loopHeuristic preState runExpr if opts.simp then (pure $ Expr.simplify exprInter) else pure exprInter @@ -437,16 +441,17 @@ mkCalldata (Just (Sig name types)) args = symCalldata name types args (AbstractBuf "txdata") verifyContract - :: SolverGroup + :: (MonadUnliftIO m, ReadConfig m) + => SolverGroup -> ByteString -> Maybe Sig -> [String] -> VeriOpts -> Maybe (Precondition RealWorld) -> Maybe (Postcondition RealWorld) - -> IO (Expr End, [VerifyResult]) + -> m (Expr End, [VerifyResult]) verifyContract solvers theCode signature' concreteArgs opts maybepre maybepost = do - preState <- stToIO $ abstractVM (mkCalldata signature' concreteArgs) theCode maybepre False + preState <- liftIO $ stToIO $ abstractVM (mkCalldata signature' concreteArgs) theCode maybepre False verify solvers opts preState maybepost -- | Stepper that parses the result of Stepper.runFully into an Expr End @@ -511,7 +516,7 @@ reachable solvers e = do pure (fst tres <> fst fres, subexpr) leaf -> do let query = assertProps abstRefineDefault pcs - res <- checkSat solvers query Nothing + res <- checkSat solvers query case res of Sat _ -> pure ([query], Just leaf) Unsat -> pure ([query], Nothing) @@ -541,53 +546,53 @@ getPartials = mapMaybe go -- | Symbolically execute the VM and check all endstates against the -- postcondition, if available. verify - :: SolverGroup + :: (MonadUnliftIO m, ReadConfig m) + => SolverGroup -> VeriOpts -> VM RealWorld -> Maybe (Postcondition RealWorld) - -> IO (Expr End, [VerifyResult]) + -> m (Expr End, [VerifyResult]) verify solvers opts preState maybepost = do - putStrLn "Exploring contract" + liftIO $ putStrLn "Exploring contract" exprInter <- interpret (Fetch.oracle solvers opts.rpcInfo) opts.maxIter opts.askSmtIters opts.loopHeuristic preState runExpr - when opts.debug $ T.writeFile "unsimplified.expr" (formatExpr exprInter) - - putStrLn "Simplifying expression" - let expr = if opts.simp then (Expr.simplify exprInter) else exprInter - when opts.debug $ T.writeFile "simplified.expr" (formatExpr expr) - - putStrLn $ "Explored contract (" <> show (Expr.numBranches expr) <> " branches)" - - let flattened = flattenExpr expr - when (any isPartial flattened) $ do - T.putStrLn "" - T.putStrLn "WARNING: hevm was only able to partially explore the given contract due to the following issues:" - T.putStrLn "" - T.putStrLn . T.unlines . fmap (indent 2 . ("- " <>)) . fmap formatPartial . getPartials $ flattened - - case maybepost of - Nothing -> pure (expr, [Qed ()]) - Just post -> do - let - -- Filter out any leaves that can be statically shown to be safe - canViolate = flip filter flattened $ - \leaf -> case Expr.evalProp (post preState leaf) of - PBool True -> False - _ -> True - assumes = preState.constraints - withQueries = canViolate <&> \leaf -> - (assertProps opts.abstRefineConfig (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf) - debugFName = if opts.debug then Just ("verify-query") else Nothing - putStrLn $ "Checking for reachability of " - <> show (length withQueries) - <> " potential property violation(s)" - - -- Dispatch the remaining branches to the solver to check for violations - results <- flip mapConcurrently withQueries $ \(query, leaf) -> do - res <- checkSat solvers query debugFName - pure (res, leaf) - let cexs = filter (\(res, _) -> not . isUnsat $ res) results - pure $ if Prelude.null cexs then (expr, [Qed ()]) else (expr, fmap toVRes cexs) + when opts.debug $ liftIO $ T.writeFile "unsimplified.expr" (formatExpr exprInter) + liftIO $ do + putStrLn "Simplifying expression" + let expr = if opts.simp then (Expr.simplify exprInter) else exprInter + when opts.debug $ liftIO $ T.writeFile "simplified.expr" (formatExpr expr) + + putStrLn $ "Explored contract (" <> show (Expr.numBranches expr) <> " branches)" + + let flattened = flattenExpr expr + when (any isPartial flattened) $ do + T.putStrLn "" + T.putStrLn "WARNING: hevm was only able to partially explore the given contract due to the following issues:" + T.putStrLn "" + T.putStrLn . T.unlines . fmap (indent 2 . ("- " <>)) . fmap formatPartial . getPartials $ flattened + + case maybepost of + Nothing -> pure (expr, [Qed ()]) + Just post -> do + let + -- Filter out any leaves that can be statically shown to be safe + canViolate = flip filter flattened $ + \leaf -> case Expr.evalProp (post preState leaf) of + PBool True -> False + _ -> True + assumes = preState.constraints + withQueries = canViolate <&> \leaf -> + (assertProps opts.abstRefineConfig (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf) + putStrLn $ "Checking for reachability of " + <> show (length withQueries) + <> " potential property violation(s)" + + -- Dispatch the remaining branches to the solver to check for violations + results <- flip mapConcurrently withQueries $ \(query, leaf) -> do + res <- checkSat solvers query + pure (res, leaf) + let cexs = filter (\(res, _) -> not . isUnsat $ res) results + pure $ if Prelude.null cexs then (expr, [Qed ()]) else (expr, fmap toVRes cexs) where toVRes :: (CheckSatResult, Expr End) -> VerifyResult toVRes (res, leaf) = case res of @@ -617,11 +622,16 @@ type UnsatCache = TVar [Set Prop] -- equivalence break, and since we run this check for every pair of end states, -- the check is exhaustive. equivalenceCheck - :: SolverGroup -> ByteString -> ByteString -> VeriOpts -> (Expr Buf, [Prop]) - -> IO [EquivResult] + :: forall m . (MonadUnliftIO m, ReadConfig m) + => SolverGroup + -> ByteString + -> ByteString + -> VeriOpts + -> (Expr Buf, [Prop]) + -> m [EquivResult] equivalenceCheck solvers bytecodeA bytecodeB opts calldata = do case bytecodeA == bytecodeB of - True -> do + True -> liftIO $ do putStrLn "bytecodeA and bytecodeB are identical" pure [Qed ()] False -> do @@ -630,41 +640,43 @@ equivalenceCheck solvers bytecodeA bytecodeB opts calldata = do equivalenceCheck' solvers branchesA branchesB opts where -- decompiles the given bytecode into a list of branches - getBranches :: ByteString -> IO [Expr End] + getBranches :: ByteString -> m [Expr End] getBranches bs = do let bytecode = if BS.null bs then BS.pack [0] else bs - prestate <- stToIO $ abstractVM calldata bytecode Nothing False + prestate <- liftIO $ stToIO $ abstractVM calldata bytecode Nothing False expr <- interpret (Fetch.oracle solvers Nothing) opts.maxIter opts.askSmtIters opts.loopHeuristic prestate runExpr let simpl = if opts.simp then (Expr.simplify expr) else expr pure $ flattenExpr simpl -equivalenceCheck' :: SolverGroup -> [Expr End] -> [Expr End] -> VeriOpts -> IO [EquivResult] +equivalenceCheck' + :: forall m . (MonadUnliftIO m, ReadConfig m) + => SolverGroup -> [Expr End] -> [Expr End] -> VeriOpts -> m [EquivResult] equivalenceCheck' solvers branchesA branchesB opts = do - when (any isPartial branchesA || any isPartial branchesB) $ do + when (any isPartial branchesA || any isPartial branchesB) $ liftIO $ do putStrLn "" putStrLn "WARNING: hevm was only able to partially explore the given contract due to the following issues:" putStrLn "" T.putStrLn . T.unlines . fmap (indent 2 . ("- " <>)) . fmap formatPartial . nubOrd $ ((getPartials branchesA) <> (getPartials branchesB)) let allPairs = [(a,b) | a <- branchesA, b <- branchesB] - putStrLn $ "Found " <> show (length allPairs) <> " total pairs of endstates" + liftIO $ putStrLn $ "Found " <> show (length allPairs) <> " total pairs of endstates" - when opts.debug $ + when opts.debug $ liftIO $ putStrLn $ "endstates in bytecodeA: " <> show (length branchesA) <> "\nendstates in bytecodeB: " <> show (length branchesB) let differingEndStates = sortBySize (mapMaybe (uncurry distinct) allPairs) - putStrLn $ "Asking the SMT solver for " <> (show $ length differingEndStates) <> " pairs" + liftIO $ putStrLn $ "Asking the SMT solver for " <> (show $ length differingEndStates) <> " pairs" when opts.debug $ forM_ (zip differingEndStates [(1::Integer)..]) (\(x, i) -> - T.writeFile ("prop-checked-" <> show i) (T.pack $ show x)) + liftIO $ T.writeFile ("prop-checked-" <> show i) (T.pack $ show x)) - knownUnsat <- newTVarIO [] - procs <- getNumProcessors - results <- checkAll differingEndStates knownUnsat procs + knownUnsat <- liftIO $ newTVarIO [] + procs <- liftIO $ getNumProcessors + results <- liftIO $ checkAll differingEndStates knownUnsat procs let useful = foldr (\(_, b) n -> if b then n+1 else n) (0::Integer) results - putStrLn $ "Reuse of previous queries was Useful in " <> (show useful) <> " cases" + liftIO $ putStrLn $ "Reuse of previous queries was Useful in " <> (show useful) <> " cases" case all isQed . fmap fst $ results of True -> pure [Qed ()] False -> pure $ filter (/= Qed ()) . fmap fst $ results @@ -682,16 +694,13 @@ equivalenceCheck' solvers branchesA branchesB opts = do -- the solver if we can determine unsatisfiability from the cache already -- the last element of the returned tuple indicates whether the cache was -- used or not - check :: UnsatCache -> (Set Prop) -> Int -> IO (EquivResult, Bool) - check knownUnsat props idx = do - let - smt = assertProps opts.abstRefineConfig (Set.toList props) - debugFName = if opts.debug then Just "equiv-query" - else Nothing + check :: UnsatCache -> (Set Prop) -> IO (EquivResult, Bool) + check knownUnsat props = do + let smt = assertProps opts.abstRefineConfig (Set.toList props) ku <- readTVarIO knownUnsat res <- if subsetAny props ku then pure (True, Unsat) - else (fmap ((False),) (checkSat solvers smt debugFName)) + else (fmap ((False),) (checkSat solvers smt)) case res of (_, Sat x) -> pure (Cex x, False) (quick, Unsat) -> @@ -712,7 +721,7 @@ equivalenceCheck' solvers branchesA branchesB opts = do checkAll :: [(Set Prop)] -> UnsatCache -> Int -> IO [(EquivResult, Bool)] checkAll input cache numproc = do wrap <- pool numproc - parMapIO (wrap . (uncurry $ check cache)) $ zip input [1..] + parMapIO (wrap . (check cache)) input -- Takes two branches and returns a set of props that will need to be @@ -784,7 +793,7 @@ produceModels solvers expr = do let flattened = flattenExpr expr withQueries = fmap (\e -> ((assertProps abstRefineDefault) . extractProps $ e, e)) flattened results <- flip mapConcurrently withQueries $ \(query, leaf) -> do - res <- checkSat solvers query Nothing + res <- checkSat solvers query pure (res, leaf) pure $ fmap swap $ filter (\(res, _) -> not . isUnsat $ res) results diff --git a/src/EVM/UnitTest.hs b/src/EVM/UnitTest.hs index 477aab5ce..036d4c6b1 100644 --- a/src/EVM/UnitTest.hs +++ b/src/EVM/UnitTest.hs @@ -43,6 +43,8 @@ import Data.Word (Word64) import GHC.Natural import System.IO (hFlush, stdout) import Witch (unsafeInto, into) +import EVM.Effects +import Control.Monad.IO.Unlift data UnitTestOptions s = UnitTestOptions { rpcInfo :: Fetch.RpcInfo @@ -103,7 +105,9 @@ makeVeriOpts opts = } -- | Top level CLI endpoint for hevm test -unitTest :: UnitTestOptions RealWorld -> Contracts -> IO Bool +unitTest + :: (MonadUnliftIO m, ReadConfig m) + => UnitTestOptions RealWorld -> Contracts -> m Bool unitTest opts (Contracts cs) = do let unitTests = findUnitTests opts.match $ Map.elems cs results <- concatMapM (runUnitTestContract opts cs) unitTests @@ -143,15 +147,16 @@ initializeUnitTest opts theContract = do _ -> popTrace runUnitTestContract - :: UnitTestOptions RealWorld + :: (MonadUnliftIO m, ReadConfig m) + => UnitTestOptions RealWorld -> Map Text SolcContract -> (Text, [Sig]) - -> IO [Bool] + -> m [Bool] runUnitTestContract opts@(UnitTestOptions {..}) contractMap (name, testSigs) = do -- Print a header - putStrLn $ "Running " ++ show (length testSigs) ++ " tests for " ++ unpack name + liftIO $ putStrLn $ "Running " ++ show (length testSigs) ++ " tests for " ++ unpack name -- Look for the wanted contract by name from the Solidity info case Map.lookup name contractMap of @@ -161,7 +166,7 @@ runUnitTestContract Just theContract -> do -- Construct the initial VM and begin the contract's constructor - vm0 <- stToIO $ initialUnitTestVm opts theContract + vm0 <- liftIO $ stToIO $ initialUnitTestVm opts theContract vm1 <- Stepper.interpret (Fetch.oracle solvers rpcInfo) vm0 $ do Stepper.enter name initializeUnitTest opts theContract @@ -179,22 +184,25 @@ runUnitTestContract -- Run all the test cases and print their status details <- forM testSigs $ \s -> do (result, detail) <- symRun opts vm1 s - Text.putStrLn result + liftIO $ Text.putStrLn result pure detail let running = rights details bailing = lefts details - tick "\n" - tick (Text.unlines (filter (not . Text.null) running)) - tick (Text.unlines bailing) + liftIO $ do + tick "\n" + tick (Text.unlines (filter (not . Text.null) running)) + tick (Text.unlines bailing) pure $ fmap isRight details _ -> internalError "setUp() did not end with a result" -- | Define the thread spawner for symbolic tests -symRun :: UnitTestOptions RealWorld -> VM RealWorld -> Sig -> IO (Text, Either Text Text) +symRun + :: (MonadUnliftIO m, ReadConfig m) + => UnitTestOptions RealWorld -> VM RealWorld -> Sig -> m (Text, Either Text Text) symRun opts@UnitTestOptions{..} vm (Sig testName types) = do let cd = symCalldata testName types [] (AbstractBuf "txdata") shouldFail = "proveFail" `isPrefixOf` testName diff --git a/test/BlockchainTests.hs b/test/BlockchainTests.hs index 1d75ef993..365519651 100644 --- a/test/BlockchainTests.hs +++ b/test/BlockchainTests.hs @@ -1,9 +1,13 @@ module Main where -import EVM.Test.BlockchainTests +import EVM.Test.BlockchainTests qualified as BlockchainTests import Test.Tasty +import EVM.Effects + +testEnv :: Env +testEnv = Env { config = Config { dumpQueries = True } } main :: IO () main = do - tests <- prepareTests + tests <- runEnv testEnv BlockchainTests.prepareTests defaultMain tests diff --git a/test/EVM/Test/BlockchainTests.hs b/test/EVM/Test/BlockchainTests.hs index 5415d2350..d598d7cd9 100644 --- a/test/EVM/Test/BlockchainTests.hs +++ b/test/EVM/Test/BlockchainTests.hs @@ -7,7 +7,7 @@ import EVM.Fetch qualified import EVM.Format (hexText) import EVM.Stepper qualified import EVM.Transaction -import EVM.Types hiding (Block, Case) +import EVM.Types hiding (Block, Case, Env) import EVM.Test.Tracing (interpretWithTrace, VMTrace, compareTraces, EVMToolTraceOutput(..)) import Optics.Core @@ -15,6 +15,8 @@ import Control.Arrow ((***), (&&&)) import Control.Monad import Control.Monad.ST (RealWorld, stToIO) import Control.Monad.State.Strict +import Control.Monad.IO.Unlift +import EVM.Effects import Data.Aeson ((.:), (.:?), FromJSON (..)) import Data.Aeson qualified as JSON import Data.Aeson.Types qualified as JSON @@ -62,38 +64,48 @@ data BlockchainCase = BlockchainCase , network :: String } deriving Show + +testEnv :: Env +testEnv = Env { config = Config { dumpQueries = True } } + main :: IO () main = do - tests <- prepareTests + tests <- runEnv testEnv prepareTests defaultMain tests -prepareTests :: IO TestTree +prepareTests :: (MonadUnliftIO m, ReadConfig m) => m TestTree prepareTests = do - repo <- getEnv "HEVM_ETHEREUM_TESTS_REPO" + repo <- liftIO $ getEnv "HEVM_ETHEREUM_TESTS_REPO" let testsDir = "BlockchainTests/GeneralStateTests" let dir = repo testsDir - jsonFiles <- Find.find Find.always (Find.extension Find.==? ".json") dir - putStrLn "Loading and parsing json files from ethereum-tests..." - isCI <- isJust <$> lookupEnv "CI" + jsonFiles <- liftIO $ Find.find Find.always (Find.extension Find.==? ".json") dir + liftIO $ putStrLn "Loading and parsing json files from ethereum-tests..." + isCI <- liftIO $ isJust <$> lookupEnv "CI" let problematicTests = if isCI then commonProblematicTests <> ciProblematicTests else commonProblematicTests let ignoredFiles = if isCI then ciIgnoredFiles else [] groups <- mapM (\f -> testGroup (makeRelative repo f) <$> (if any (`isInfixOf` f) ignoredFiles then pure [] else testsFromFile f problematicTests)) jsonFiles - putStrLn "Loaded." + liftIO $ putStrLn "Loaded." pure $ testGroup "ethereum-tests" groups -testsFromFile :: String -> Map String (TestTree -> TestTree) -> IO [TestTree] +testsFromFile + :: forall m . (MonadUnliftIO m, ReadConfig m) + => String -> Map String (TestTree -> TestTree) -> m [TestTree] testsFromFile file problematicTests = do - parsed <- parseBCSuite <$> LazyByteString.readFile file + parsed <- parseBCSuite <$> (liftIO $ LazyByteString.readFile file) case parsed of Left "No cases to check." -> pure [] -- error "no-cases ok" Left _err -> pure [] -- error _err - Right allTests -> pure $ - (\(name, x) -> testCase' name $ runVMTest True (name, x)) <$> Map.toList allTests + Right allTests -> mapM stuff (Map.toList allTests) where - testCase' name assertion = - case Map.lookup name problematicTests of - Just f -> f (testCase name assertion) - Nothing -> testCase name assertion + stuff :: (String , Case) -> m TestTree + stuff (name, x) = do + exec <- toIO $ runVMTest True (name, x) + pure $ testCase' name exec + testCase' :: String -> Assertion -> TestTree + testCase' name assertion = + case Map.lookup name problematicTests of + Just f -> f (testCase name (liftIO assertion)) + Nothing -> testCase name (liftIO assertion) -- CI has issues with some heaver tests, disable in bulk ciIgnoredFiles :: [String] @@ -126,24 +138,34 @@ ciProblematicTests = Map.fromList , ("loopExp_d9g0v0_Shanghai", ignoreTest) ] -runVMTest :: Bool -> (String, Case) -> IO () +runVMTest + :: (MonadUnliftIO m, ReadConfig m) + => Bool -> (String, Case) -> m () runVMTest diffmode (_name, x) = do - vm0 <- vmForCase x + vm0 <- liftIO $ vmForCase x result <- EVM.Stepper.interpret (EVM.Fetch.zero 0 (Just 0)) vm0 EVM.Stepper.runFully - maybeReason <- checkExpectation diffmode x result - forM_ maybeReason assertFailure + liftIO $ do + maybeReason <- checkExpectation diffmode x result + forM_ maybeReason assertFailure + -- | Run a vm test and output a geth style per opcode trace -traceVMTest :: String -> String -> IO [VMTrace] +traceVMTest + :: (MonadUnliftIO m, ReadConfig m) + => String -> String -> m [VMTrace] traceVMTest file test = do - repo <- getEnv "HEVM_ETHEREUM_TESTS_REPO" - Right allTests <- parseBCSuite <$> LazyByteString.readFile (repo file) - let x = case filter (\(name, _) -> name == test) $ Map.toList allTests of + repo <- liftIO $ getEnv "HEVM_ETHEREUM_TESTS_REPO" + allTests <- parseBCSuite <$> (liftIO $ LazyByteString.readFile (repo file)) + let x = case filter (\(name, _) -> name == test) $ Map.toList (getRight allTests) of [(_, x')] -> x' _ -> internalError "test not found" - vm0 <- vmForCase x + vm0 <- liftIO $ vmForCase x (_, (_, ts)) <- runStateT (interpretWithTrace (EVM.Fetch.zero 0 (Just 0)) EVM.Stepper.runFully) (vm0, []) pure ts + where + getRight :: Either a b -> b + getRight (Right a) = a + getRight (Left _) = error "Not allowed" -- | Read a geth trace from disk readTrace :: FilePath -> IO (Either String EVMToolTraceOutput) @@ -151,11 +173,14 @@ readTrace = JSON.eitherDecodeFileStrict -- | given a path to a test file, a test case from within that file, and a trace from geth from running that test, compare the traces and show where we differ -- This would need a few tweaks to geth to make this really usable (i.e. evm statetest show allow running a single test from within the test file). -traceVsGeth :: String -> String -> FilePath -> IO () +traceVsGeth + :: (MonadUnliftIO m, ReadConfig m) + => String -> String -> FilePath -> m () traceVsGeth file test gethTrace = do hevm <- traceVMTest file test - EVMToolTraceOutput ts _ <- fromJust <$> (JSON.decodeFileStrict gethTrace :: IO (Maybe EVMToolTraceOutput)) - _ <- compareTraces hevm ts + decodedContents <- liftIO (JSON.decodeFileStrict gethTrace :: IO (Maybe EVMToolTraceOutput)) + let EVMToolTraceOutput ts _ = fromJust decodedContents + _ <- liftIO $ compareTraces hevm ts pure () splitEithers :: (Filterable f) => f (Either a b) -> (f a, f b) diff --git a/test/EVM/Test/Tracing.hs b/test/EVM/Test/Tracing.hs index 996345826..8041e6b2b 100644 --- a/test/EVM/Test/Tracing.hs +++ b/test/EVM/Test/Tracing.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} {-| Module : Tracing @@ -14,8 +15,9 @@ module EVM.Test.Tracing where import Control.Monad (when) import Control.Monad.Operational qualified as Operational import Control.Monad.ST (RealWorld, ST, stToIO) -import Control.Monad.State.Strict (StateT(..), liftIO) +import Control.Monad.State.Strict (StateT(..)) import Control.Monad.State.Strict qualified as State +import Control.Monad.Reader (ReaderT) import Data.Aeson ((.:), (.:?)) import Data.Aeson qualified as JSON import Data.ByteString (ByteString) @@ -36,7 +38,7 @@ import Test.QuickCheck (elements) import Test.QuickCheck.Instances.Text() import Test.QuickCheck.Instances.Natural() import Test.QuickCheck.Instances.ByteString() -import Test.Tasty (testGroup, TestTree) +import Test.Tasty (testGroup, TestTree, TestName) import Test.Tasty.HUnit (assertEqual, testCase) import Test.Tasty.QuickCheck hiding (Failure, Success) import Witch (into, unsafeInto) @@ -59,7 +61,9 @@ import EVM.Stepper qualified as Stepper import EVM.SymExec import EVM.Traversals (mapExpr) import EVM.Transaction qualified -import EVM.Types +import EVM.Types hiding (Env) +import EVM.Effects +import Control.Monad.IO.Unlift data VMTrace = VMTrace @@ -295,7 +299,9 @@ evmSetup contr txData gaslimitExec = (txn, evmEnv, contrAlloc, fromAddress, toAd fromAddress = fromJust $ deriveAddr sk toAddress = 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 -getHEVMRet :: OpContract -> ByteString -> Int -> IO (Either (EvmError, [VMTrace]) (Expr 'End, [VMTrace], VMTraceResult)) +getHEVMRet + :: (MonadUnliftIO m, ReadConfig m) + => OpContract -> ByteString -> Int -> m (Either (EvmError, [VMTrace]) (Expr 'End, [VMTrace], VMTraceResult)) getHEVMRet contr txData gaslimitExec = do let (txn, evmEnv, contrAlloc, fromAddress, toAddress, _) = evmSetup contr txData gaslimitExec runCodeWithTrace Nothing evmEnv contrAlloc txn (LitAddr fromAddress) (LitAddr toAddress) @@ -413,13 +419,15 @@ symbolify vm = vm { state = vm.state { calldata = AbstractBuf "calldata" } } -- | Takes a runtime code and calls it with the provided calldata -- Uses evmtool's alloc and transaction to set up the VM correctly -runCodeWithTrace :: Fetch.RpcInfo -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction -> Expr EAddr -> Expr EAddr -> IO (Either (EvmError, [VMTrace]) ((Expr 'End, [VMTrace], VMTraceResult))) +runCodeWithTrace + :: (MonadUnliftIO m, ReadConfig m) + => Fetch.RpcInfo -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction + -> Expr EAddr -> Expr EAddr -> m (Either (EvmError, [VMTrace]) ((Expr 'End, [VMTrace], VMTraceResult))) runCodeWithTrace rpcinfo evmEnv alloc txn fromAddr toAddress = withSolvers Z3 0 Nothing $ \solvers -> do let calldata' = ConcreteBuf txn.txdata code' = alloc.code - buildExpr :: SolverGroup -> VM RealWorld -> IO (Expr End) buildExpr s vm = interpret (Fetch.oracle s Nothing) Nothing 1 Naive vm runExpr - origVM <- stToIO $ vmForRuntimeCode code' calldata' evmEnv alloc txn fromAddr toAddress + origVM <- liftIO $ stToIO $ vmForRuntimeCode code' calldata' evmEnv alloc txn fromAddr toAddress expr <- buildExpr solvers $ symbolify origVM (res, (vm, trace)) <- runStateT (interpretWithTrace (Fetch.oracle solvers rpcinfo) Stepper.execFully) (origVM, []) @@ -460,9 +468,11 @@ vmForRuntimeCode runtimecode calldata' evmToolEnv alloc txn fromAddr toAddress = (Just (initialContract (RuntimeCode (ConcreteRuntimeCode BS.empty)))) <&> set (#state % #calldata) calldata' -runCode :: Fetch.RpcInfo -> ByteString -> Expr Buf -> IO (Maybe (Expr Buf)) +runCode + :: (MonadUnliftIO m, ReadConfig m) + => Fetch.RpcInfo -> ByteString -> Expr Buf -> m (Maybe (Expr Buf)) runCode rpcinfo code' calldata' = withSolvers Z3 0 Nothing $ \solvers -> do - origVM <- stToIO $ vmForRuntimeCode + origVM <- liftIO $ stToIO $ vmForRuntimeCode code' calldata' emptyEvmToolEnv @@ -530,12 +540,16 @@ vmres vm = type TraceState s = (VM s, [VMTrace]) -execWithTrace :: StateT (TraceState RealWorld) IO (VMResult RealWorld) +execWithTrace + :: (MonadUnliftIO m, ReadConfig m) + => StateT (TraceState RealWorld) m (VMResult RealWorld) execWithTrace = do _ <- runWithTrace fromJust <$> use (_1 % #result) -runWithTrace :: StateT (TraceState RealWorld) IO (VM RealWorld) +runWithTrace + :: (MonadUnliftIO m, ReadConfig m) + => StateT (TraceState RealWorld) m (VM RealWorld) runWithTrace = do -- This is just like `exec` except for every instruction evaluated, -- we also increment a counter indexed by the current code location. @@ -556,20 +570,18 @@ runWithTrace = do Just _ -> pure vm0 interpretWithTrace - :: Fetch.Fetcher RealWorld + :: forall m a . (MonadUnliftIO m, ReadConfig m) + => Fetch.Fetcher m RealWorld -> Stepper.Stepper RealWorld a - -> StateT (TraceState RealWorld) IO a + -> StateT (TraceState RealWorld) m a interpretWithTrace fetcher = eval . Operational.view - where eval - :: Operational.ProgramView (Stepper.Action RealWorld) a - -> StateT (TraceState RealWorld) IO a - - eval (Operational.Return x) = - pure x - + :: (MonadUnliftIO m, ReadConfig m) + => Operational.ProgramView (Stepper.Action RealWorld) a + -> StateT (TraceState RealWorld) m a + eval (Operational.Return x) = pure x eval (action Operational.:>>= k) = case action of Stepper.Exec -> @@ -578,7 +590,7 @@ interpretWithTrace fetcher = PleaseAskSMT (Lit x) _ continue -> interpretWithTrace fetcher (Stepper.evm (continue (Case (x > 0))) >>= k) _ -> do - m <- liftIO (fetcher q) + m <- State.lift $ fetcher q vm <- use _1 vm' <- liftIO $ stToIO $ State.execStateT m vm assign _1 vm' @@ -820,29 +832,38 @@ getOp vm = in if xs == BS.empty then 0 else BS.head xs +testEnv :: Env +testEnv = Env { config = Config { dumpQueries = True } } + +test :: TestName -> ReaderT Env IO () -> TestTree +test a b = testCase a $ runEnv testEnv b + +prop:: Testable prop => ReaderT Env IO prop -> Property +prop a = ioProperty $ runEnv testEnv a + tests :: TestTree tests = testGroup "contract-quickcheck-run" - [ testProperty "random-contract-concrete-call" $ \(contr :: OpContract, GasLimitInt gasLimit, TxDataRaw txDataRaw) -> ioProperty $ do + [ testProperty "random-contract-concrete-call" $ \(contr :: OpContract, GasLimitInt gasLimit, TxDataRaw txDataRaw) -> prop $ do let txData = BS.pack $ toEnum <$> txDataRaw -- TODO: By removing external calls, we fuzz less -- It should work also when we external calls. Removing for now. - contrFixed <- fixContractJumps $ removeExtcalls contr + contrFixed <- liftIO $ fixContractJumps $ removeExtcalls contr checkTraceAndOutputs contrFixed gasLimit txData - , testCase "calldata-wraparound" $ do + , test "calldata-wraparound" $ do let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 31),OpMstore8] -- value, offs , [OpPush (Lit 0x3),OpPush (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff),OpPush (Lit 0x0),OpCalldatacopy] -- size, offs, destOffs , [OpPush (Lit 0x20),OpPush (Lit 0),OpReturn] -- datasize, offs ] checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5]) - , testCase "calldata-wraparound2" $ do + , test "calldata-wraparound2" $ do let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 0),OpMstore8] -- value, offs , [OpPush (Lit 0x10),OpPush (Lit 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff),OpPush (Lit 0x0),OpCalldatacopy] -- size, offs, destOffs , [OpPush (Lit 0x20),OpPush (Lit 0),OpReturn] -- datasize, offs ] checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5]) - , testCase "calldata-overwrite-with-0-if-oversized" $ do + , test "calldata-overwrite-with-0-if-oversized" $ do -- supposed to copy 1...6 and then 0s, overwriting the 0xff with 0 let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 1),OpMstore8] -- value, offs @@ -850,14 +871,14 @@ tests = testGroup "contract-quickcheck-run" , [OpPush (Lit 10),OpPush (Lit 0x0),OpReturn] -- datasize, offset ] checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5, 6]) - , testCase "calldata-overwrite-correct-size" $ do + , test "calldata-overwrite-correct-size" $ do let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 8),OpMstore8] -- value, offs , [OpPush (Lit 10),OpPush (Lit 0),OpPush (Lit 0), OpCalldatacopy] -- size, offs, destOffs , [OpPush (Lit 10),OpPush (Lit 0x0),OpReturn] -- datasize, offset ] checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5, 6]) - , testCase "calldata-offset-copy" $ do + , test "calldata-offset-copy" $ do let contract = OpContract $ concat [ [OpPush (Lit 0xff),OpPush (Lit 8),OpMstore8] -- value, offs , [OpPush (Lit 0xff),OpPush (Lit 1),OpMstore8] -- value, offs @@ -867,13 +888,15 @@ tests = testGroup "contract-quickcheck-run" checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5, 6]) ] -checkTraceAndOutputs :: OpContract -> Int -> ByteString -> IO () +checkTraceAndOutputs + :: (MonadUnliftIO m, ReadConfig m) + => OpContract -> Int -> ByteString -> m () checkTraceAndOutputs contract gasLimit txData = do - evmtoolResult <- getEVMToolRet contract txData gasLimit + evmtoolResult <- liftIO $ getEVMToolRet contract txData gasLimit hevmRun <- getHEVMRet contract txData gasLimit - (Just evmtoolTraceOutput) <- getTraceOutput evmtoolResult + evmtoolTraceOutput <- fmap fromJust $ liftIO $ getTraceOutput evmtoolResult case hevmRun of - (Right (expr, hevmTrace, hevmTraceResult)) -> do + (Right (expr, hevmTrace, hevmTraceResult)) -> liftIO $ do let concretize :: Expr a -> Expr Buf -> Expr a concretize a c = mapExpr go a @@ -893,7 +916,7 @@ checkTraceAndOutputs contract gasLimit txData = do -- putStrLn $ "evmtool trace: " <> show (evmtoolTraceOutput.trace) assertEqual "Traces and gas must match" traceOK True let resultOK = evmtoolTraceOutput.output.output == hevmTraceResult.out - if resultOK then do + if resultOK then liftIO $ do putStrLn $ "HEVM & evmtool's outputs match: '" <> (bsToHex $ bssToBs evmtoolTraceOutput.output.output) <> "'" if isNothing simplConcrExprRetval || (fromJust simplConcrExprRetval) == (bssToBs hevmTraceResult.out) then do @@ -916,18 +939,19 @@ checkTraceAndOutputs contract gasLimit txData = do putStrLn $ "HEVM result len: " <> (show (BS.length $ bssToBs hevmTraceResult.out)) putStrLn $ "evm result len: " <> (show (BS.length $ bssToBs evmtoolTraceOutput.output.output)) assertEqual "Contract exec successful. HEVM & evmtool's outputs must match" resultOK True - Left (evmerr, hevmTrace) -> do + Left (evmerr, hevmTrace) -> liftIO $ do putStrLn $ "HEVM contract exec issue: " <> (show evmerr) -- putStrLn $ "evmtool result was: " <> show (fromJust evmtoolResult) -- putStrLn $ "output by evmtool is: '" <> bsToHex evmtoolTraceOutput.toOutput.output <> "'" traceOK <- compareTraces hevmTrace (evmtoolTraceOutput.trace) assertEqual "Traces and gas must match" traceOK True - System.Directory.removeFile "txs.json" - System.Directory.removeFile "alloc-out.json" - System.Directory.removeFile "alloc.json" - System.Directory.removeFile "result.json" - System.Directory.removeFile "env.json" - deleteTraceOutputFiles evmtoolResult + liftIO $ do + System.Directory.removeFile "txs.json" + System.Directory.removeFile "alloc-out.json" + System.Directory.removeFile "alloc.json" + System.Directory.removeFile "result.json" + System.Directory.removeFile "env.json" + deleteTraceOutputFiles evmtoolResult -- GasLimitInt newtype GasLimitInt = GasLimitInt (Int) diff --git a/test/EVM/Test/Utils.hs b/test/EVM/Test/Utils.hs index ce079f253..f11e7d7da 100644 --- a/test/EVM/Test/Utils.hs +++ b/test/EVM/Test/Utils.hs @@ -20,18 +20,25 @@ import EVM.Solidity import EVM.Solvers import EVM.UnitTest import Control.Monad.ST (RealWorld) +import Control.Monad.IO.Unlift +import Control.Monad.Catch (MonadMask) +import EVM.Effects -runSolidityTestCustom :: FilePath -> Text -> Maybe Natural -> Maybe Integer -> Bool -> RpcInfo -> ProjectType -> IO Bool +runSolidityTestCustom + :: (MonadMask m, MonadUnliftIO m, ReadConfig m) + => FilePath -> Text -> Maybe Natural -> Maybe Integer -> Bool -> RpcInfo -> ProjectType -> m Bool runSolidityTestCustom testFile match timeout maxIter ffiAllowed rpcinfo projectType = do withSystemTempDirectory "dapp-test" $ \root -> do - compile projectType root testFile >>= \case + (liftIO $ compile projectType root testFile) >>= \case Left e -> error e Right bo@(BuildOutput contracts _) -> do withSolvers Z3 1 timeout $ \solvers -> do - opts <- testOpts solvers root (Just bo) match maxIter ffiAllowed rpcinfo + opts <- liftIO $ testOpts solvers root (Just bo) match maxIter ffiAllowed rpcinfo unitTest opts contracts -runSolidityTest :: FilePath -> Text -> IO Bool +runSolidityTest + :: (MonadMask m, MonadUnliftIO m, ReadConfig m) + => FilePath -> Text -> m Bool runSolidityTest testFile match = runSolidityTestCustom testFile match Nothing Nothing True Nothing Foundry testOpts :: SolverGroup -> FilePath -> Maybe BuildOutput -> Text -> Maybe Integer -> Bool -> RpcInfo -> IO (UnitTestOptions RealWorld) diff --git a/test/test.hs b/test/test.hs index 190ec1946..4a6e03817 100644 --- a/test/test.hs +++ b/test/test.hs @@ -9,6 +9,8 @@ import GHC.TypeLits import Data.Proxy import Control.Monad.ST (RealWorld, stToIO) import Control.Monad.State.Strict +import Control.Monad.IO.Unlift +import Control.Monad.Reader (ReaderT) import Data.Bits hiding (And, Xor) import Data.ByteString (ByteString) import Data.ByteString qualified as BS @@ -64,7 +66,30 @@ import EVM.SymExec import EVM.Test.Tracing qualified as Tracing import EVM.Test.Utils import EVM.Traversals -import EVM.Types +import EVM.Types hiding (Env) +import EVM.Effects + +testEnv :: Env +testEnv = Env { config = Config { dumpQueries = False } } + +putStrLnM :: (MonadUnliftIO m) => String -> m () +putStrLnM a = liftIO $ putStrLn a + +assertEqualM + :: (MonadUnliftIO m, ReadConfig m, Eq a, Show a, HasCallStack) + => String -> a -> a -> m () +assertEqualM a b c = liftIO $ assertEqual a b c + +assertBoolM + :: (MonadUnliftIO m, HasCallStack) + => String -> Bool -> m () +assertBoolM a b = liftIO $ assertBool a b + +test :: TestName -> ReaderT Env IO () -> TestTree +test a b = testCase a $ runEnv testEnv b + +prop:: Testable prop => ReaderT Env IO prop -> Property +prop a = ioProperty $ runEnv testEnv a main :: IO () main = defaultMain tests @@ -78,7 +103,7 @@ tests :: TestTree tests = testGroup "hevm" [ Tracing.tests , testGroup "simplify-storage" - [ testCase "simplify-storage-array-only-static" $ do + [ test "simplify-storage-array-only-static" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -93,12 +118,12 @@ tests = testGroup "hevm" } |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts - assertEqual "Expression is not clean." (badStoresInExpr expr) False + assertEqualM "Expression is not clean." (badStoresInExpr expr) False -- This case is somewhat artificial. We can't simplify this using only -- static rewrite rules, because acct is totally abstract and acct + 1 -- could overflow back to zero. we may be able to do better if we have some -- smt assisted simplification that can take branch conditions into account. - , expectFail $ testCase "simplify-storage-array-symbolic-index" $ do + , expectFail $ test "simplify-storage-array-symbolic-index" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -114,8 +139,8 @@ tests = testGroup "hevm" |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts -- T.writeFile "symbolic-index.expr" $ formatExpr expr - assertEqual "Expression is not clean." (badStoresInExpr expr) False - , expectFail $ testCase "simplify-storage-array-of-struct-symbolic-index" $ do + assertEqualM "Expression is not clean." (badStoresInExpr expr) False + , expectFail $ test "simplify-storage-array-of-struct-symbolic-index" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -134,8 +159,8 @@ tests = testGroup "hevm" } |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts - assertEqual "Expression is not clean." (badStoresInExpr expr) False - , testCase "simplify-storage-array-loop-nonstruct" $ do + assertEqualM "Expression is not clean." (badStoresInExpr expr) False + , test "simplify-storage-array-loop-nonstruct" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -149,8 +174,8 @@ tests = testGroup "hevm" } |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256)" [AbiUIntType 256])) [] (debugVeriOpts { maxIter = Just 5 }) - assertEqual "Expression is not clean." (badStoresInExpr expr) False - , testCase "simplify-storage-array-loop-struct" $ do + assertEqualM "Expression is not clean." (badStoresInExpr expr) False + , test "simplify-storage-array-loop-struct" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -169,8 +194,8 @@ tests = testGroup "hevm" } |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] (debugVeriOpts { maxIter = Just 5 }) - assertEqual "Expression is not clean." (badStoresInExpr expr) False - , testCase "simplify-storage-map-only-static" $ do + assertEqualM "Expression is not clean." (badStoresInExpr expr) False + , test "simplify-storage-map-only-static" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -185,8 +210,8 @@ tests = testGroup "hevm" } |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts - assertEqual "Expression is not clean." (badStoresInExpr expr) False - , testCase "simplify-storage-map-only-2" $ do + assertEqualM "Expression is not clean." (badStoresInExpr expr) False + , test "simplify-storage-map-only-2" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -201,9 +226,9 @@ tests = testGroup "hevm" } |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts - -- putStrLn $ T.unpack $ formatExpr expr - assertEqual "Expression is not clean." (badStoresInExpr expr) False - , testCase "simplify-storage-map-with-struct" $ do + -- putStrLnM $ T.unpack $ formatExpr expr + assertEqualM "Expression is not clean." (badStoresInExpr expr) False + , test "simplify-storage-map-with-struct" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -222,8 +247,8 @@ tests = testGroup "hevm" } |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts - assertEqual "Expression is not clean." (badStoresInExpr expr) False - , testCase "simplify-storage-map-and-array" $ do + assertEqualM "Expression is not clean." (badStoresInExpr expr) False + , test "simplify-storage-map-and-array" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -244,53 +269,53 @@ tests = testGroup "hevm" } |] expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts - -- putStrLn $ T.unpack $ formatExpr expr - assertEqual "Expression is not clean." (badStoresInExpr expr) False + -- putStrLnM $ T.unpack $ formatExpr expr + assertEqualM "Expression is not clean." (badStoresInExpr expr) False ] , testGroup "StorageTests" - [ testCase "read-from-sstore" $ assertEqual "" + [ test "read-from-sstore" $ assertEqualM "" (Lit 0xab) (Expr.readStorage' (Lit 0x0) (SStore (Lit 0x0) (Lit 0xab) (AbstractStore (LitAddr 0x0)))) - , testCase "read-from-concrete" $ assertEqual "" + , test "read-from-concrete" $ assertEqualM "" (Lit 0xab) (Expr.readStorage' (Lit 0x0) (ConcreteStore $ Map.fromList [(0x0, 0xab)])) - , testCase "read-past-write" $ assertEqual "" + , test "read-past-write" $ assertEqualM "" (Lit 0xab) (Expr.readStorage' (Lit 0x0) (SStore (Lit 0x1) (Var "b") (ConcreteStore $ Map.fromList [(0x0, 0xab)]))) - , testCase "accessStorage uses fetchedStorage" $ do + , test "accessStorage uses fetchedStorage" $ do let dummyContract = (initialContract (RuntimeCode (ConcreteRuntimeCode mempty))) { external = True } - vm <- stToIO $ vmForEthrunCreation "" + vm <- liftIO $ stToIO $ vmForEthrunCreation "" -- perform the initial access - vm1 <- stToIO $ execStateT (EVM.accessStorage (LitAddr 0) (Lit 0) (pure . pure ())) vm + vm1 <- liftIO $ stToIO $ execStateT (EVM.accessStorage (LitAddr 0) (Lit 0) (pure . pure ())) vm -- it should fetch the contract first vm2 <- case vm1.result of Just (HandleEffect (Query (PleaseFetchContract _addr _ continue))) -> - stToIO $ execStateT (continue dummyContract) vm1 + liftIO $ stToIO $ execStateT (continue dummyContract) vm1 _ -> internalError "unexpected result" -- then it should fetch the slow vm3 <- case vm2.result of Just (HandleEffect (Query (PleaseFetchSlot _addr _slot continue))) -> - stToIO $ execStateT (continue 1337) vm2 + liftIO $ stToIO $ execStateT (continue 1337) vm2 _ -> internalError "unexpected result" -- perform the same access as for vm1 - vm4 <- stToIO $ execStateT (EVM.accessStorage (LitAddr 0) (Lit 0) (pure . pure ())) vm3 + vm4 <- liftIO $ stToIO $ execStateT (EVM.accessStorage (LitAddr 0) (Lit 0) (pure . pure ())) vm3 -- there won't be query now as accessStorage uses fetch cache - assertBool (show vm4.result) (isNothing vm4.result) + assertBoolM (show vm4.result) (isNothing vm4.result) ] , testGroup "SimplifierUnitTests" -- common overflow cases that the simplifier was getting wrong - [ testCase "writeWord-overflow" $ do + [ test "writeWord-overflow" $ do let e = ReadByte (Lit 0x0) (WriteWord (Lit 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd) (Lit 0x0) (ConcreteBuf "\255\255\255\255")) b <- checkEquiv e (Expr.simplify e) - assertBool "Simplifier failed" b - , testCase "CopySlice-overflow" $ do + assertBoolM "Simplifier failed" b + , test "CopySlice-overflow" $ do let e = ReadWord (Lit 0x0) (CopySlice (Lit 0x0) (Lit 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc) (Lit 0x6) (ConcreteBuf "\255\255\255\255\255\255") (ConcreteBuf "")) b <- checkEquiv e (Expr.simplify e) - assertBool "Simplifier failed" b - , testCase "stripWrites-overflow" $ do + assertBoolM "Simplifier failed" b + , test "stripWrites-overflow" $ do -- below eventually boils down to -- unsafeInto (0xf0000000000000000000000000000000000000000000000000000000000000+1) :: Int -- which failed before @@ -298,66 +323,66 @@ tests = testGroup "hevm" a = ReadByte (Lit 0xf0000000000000000000000000000000000000000000000000000000000000) (WriteByte (And (SHA256 (ConcreteBuf "")) (Lit 0x1)) (LitByte 0) (ConcreteBuf "")) b = Expr.simplify a ret <- checkEquiv a b - assertBool "must be equivalent" ret + assertBoolM "must be equivalent" ret ] -- These tests fuzz the simplifier by generating a random expression, -- applying some simplification rules, and then using the smt encoding to -- check that the simplified version is semantically equivalent to the -- unsimplified one , adjustOption (\(Test.Tasty.QuickCheck.QuickCheckTests n) -> Test.Tasty.QuickCheck.QuickCheckTests (min n 50)) $ testGroup "SimplifierTests" - [ testProperty "buffer-simplification" $ \(expr :: Expr Buf) -> ioProperty $ do + [ testProperty "buffer-simplification" $ \(expr :: Expr Buf) -> prop $ do let simplified = Expr.simplify expr checkEquiv expr simplified - , testProperty "store-simplification" $ \(expr :: Expr Storage) -> ioProperty $ do + , testProperty "store-simplification" $ \(expr :: Expr Storage) -> prop $ do let simplified = Expr.simplify expr checkEquiv expr simplified - , testProperty "byte-simplification" $ \(expr :: Expr Byte) -> ioProperty $ do + , testProperty "byte-simplification" $ \(expr :: Expr Byte) -> prop $ do let simplified = Expr.simplify expr checkEquiv expr simplified - , testProperty "word-simplification" $ \(ZeroDepthWord expr) -> ioProperty $ do + , testProperty "word-simplification" $ \(ZeroDepthWord expr) -> prop $ do let simplified = Expr.simplify expr checkEquiv expr simplified - , testProperty "readStorage-equivalance" $ \(store, slot) -> ioProperty $ do + , testProperty "readStorage-equivalance" $ \(store, slot) -> prop $ do let simplified = Expr.readStorage' slot store full = SLoad slot store checkEquiv simplified full - , testProperty "writeStorage-equivalance" $ \(val, GenWriteStorageExpr (slot, store)) -> ioProperty $ do + , testProperty "writeStorage-equivalance" $ \(val, GenWriteStorageExpr (slot, store)) -> prop $ do let simplified = Expr.writeStorage slot val store full = SStore slot val store checkEquiv simplified full - , testProperty "readWord-equivalance" $ \(buf, idx) -> ioProperty $ do + , testProperty "readWord-equivalance" $ \(buf, idx) -> prop $ do let simplified = Expr.readWord idx buf full = ReadWord idx buf checkEquiv simplified full - , testProperty "writeWord-equivalance" $ \(idx, val, WriteWordBuf buf) -> ioProperty $ do + , testProperty "writeWord-equivalance" $ \(idx, val, WriteWordBuf buf) -> prop $ do let simplified = Expr.writeWord idx val buf full = WriteWord idx val buf checkEquiv simplified full - , testProperty "arith-simplification" $ \(_ :: Int) -> ioProperty $ do - expr <- generate . sized $ genWordArith 15 + , testProperty "arith-simplification" $ \(_ :: Int) -> prop $ do + expr <- liftIO $ generate . sized $ genWordArith 15 let simplified = Expr.simplify expr checkEquiv expr simplified - , testProperty "readByte-equivalance" $ \(buf, idx) -> ioProperty $ do + , testProperty "readByte-equivalance" $ \(buf, idx) -> prop $ do let simplified = Expr.readByte idx buf full = ReadByte idx buf checkEquiv simplified full -- we currently only simplify concrete writes over concrete buffers so that's what we test here - , testProperty "writeByte-equivalance" $ \(LitOnly val, LitOnly buf, GenWriteByteIdx idx) -> ioProperty $ do + , testProperty "writeByte-equivalance" $ \(LitOnly val, LitOnly buf, GenWriteByteIdx idx) -> prop $ do let simplified = Expr.writeByte idx val buf full = WriteByte idx val buf checkEquiv simplified full - , testProperty "copySlice-equivalance" $ \(srcOff, GenCopySliceBuf src, GenCopySliceBuf dst, LitWord @300 size) -> ioProperty $ do + , testProperty "copySlice-equivalance" $ \(srcOff, GenCopySliceBuf src, GenCopySliceBuf dst, LitWord @300 size) -> prop $ do -- we bias buffers to be concrete more often than not - dstOff <- generate (maybeBoundedLit 100_000) + dstOff <- liftIO $ generate (maybeBoundedLit 100_000) let simplified = Expr.copySlice srcOff dstOff size src dst full = CopySlice srcOff dstOff size src dst checkEquiv simplified full - , testProperty "indexWord-equivalence" $ \(src, LitWord @50 idx) -> ioProperty $ do + , testProperty "indexWord-equivalence" $ \(src, LitWord @50 idx) -> prop $ do let simplified = Expr.indexWord idx src full = IndexWord idx src checkEquiv simplified full - , testProperty "indexWord-mask-equivalence" $ \(src :: Expr EWord, LitWord @35 idx) -> ioProperty $ do - mask <- generate $ do + , testProperty "indexWord-mask-equivalence" $ \(src :: Expr EWord, LitWord @35 idx) -> prop $ do + mask <- liftIO $ generate $ do pow <- arbitrary :: Gen Int frequency [ (1, pure $ Lit $ (shiftL 1 (pow `mod` 256)) - 1) -- potentially non byte aligned @@ -368,7 +393,7 @@ tests = testGroup "hevm" simplified = Expr.indexWord idx input full = IndexWord idx input checkEquiv simplified full - , testProperty "toList-equivalance" $ \buf -> ioProperty $ do + , testProperty "toList-equivalance" $ \buf -> prop $ do let -- transforms the input buffer to give it a known length fixLength :: Expr Buf -> Gen (Expr Buf) @@ -389,161 +414,159 @@ tests = testGroup "hevm" -- we use 100_000 as an upper bound for indices to keep tests reasonably fast here pure $ Lit (w `mod` 100_000) - input <- generate $ fixLength buf + input <- liftIO $ generate $ fixLength buf case Expr.toList input of Nothing -> do - putStrLn "skip" + putStrLnM "skip" pure True -- ignore cases where the buf cannot be represented as a list Just asList -> do let asBuf = Expr.fromList asList checkEquiv asBuf input - , testProperty "evalProp-equivalence-lit" $ \(LitProp p) -> ioProperty $ do + , testProperty "evalProp-equivalence-lit" $ \(LitProp p) -> prop $ do let simplified = Expr.simplifyProps [p] case simplified of [] -> checkEquivProp (PBool True) p [val@(PBool _)] -> checkEquivProp val p - _ -> assertFailure "must evaluate down to a literal bool" - , testProperty "evalProp-equivalence-sym" $ \(p) -> ioProperty $ do + _ -> liftIO $ assertFailure "must evaluate down to a literal bool" + , testProperty "evalProp-equivalence-sym" $ \(p) -> prop $ do let simplified = Expr.evalProp p checkEquivProp simplified p - , testProperty "simpProp-equivalence-sym" $ \(ps :: [Prop]) -> ioProperty $ do + , testProperty "simpProp-equivalence-sym" $ \(ps :: [Prop]) -> prop $ do let simplified = pand (Expr.simplifyProps ps) checkEquivProp simplified (pand ps) - , testProperty "simpProp-equivalence-sym" $ \(LitProp p) -> ioProperty $ do + , testProperty "simpProp-equivalence-sym" $ \(LitProp p) -> prop $ do let simplified = pand (Expr.simplifyProps [p]) checkEquivProp simplified p -- This would need to be a fuzz test I think. The SMT encoding of Keccak is not precise -- enough for this to succeed - , ignoreTest $ testProperty "storage-slot-simp-property" $ \(StorageExp s) -> ioProperty $ do - T.writeFile "unsimplified.expr" $ formatExpr s + , ignoreTest $ testProperty "storage-slot-simp-property" $ \(StorageExp s) -> prop $ do let simplified = Expr.simplify s - T.writeFile "simplified.expr" $ formatExpr simplified checkEquiv simplified s ] , testGroup "simpProp-concrete-tests" [ - testCase "simpProp-concrete-trues" $ do + test "simpProp-concrete-trues" $ do let t = [PBool True, PBool True] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [] simplified - , testCase "simpProp-concrete-false1" $ do + assertEqualM "Must be equal" [] simplified + , test "simpProp-concrete-false1" $ do let t = [PBool True, PBool False] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [PBool False] simplified - , testCase "simpProp-concrete-false2" $ do + assertEqualM "Must be equal" [PBool False] simplified + , test "simpProp-concrete-false2" $ do let t = [PBool False, PBool False] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [PBool False] simplified - , testCase "simpProp-concrete-or-1" $ do + assertEqualM "Must be equal" [PBool False] simplified + , test "simpProp-concrete-or-1" $ do let -- a = 5 && (a=4 || a=3) -> False t = [PEq (Lit 5) (Var "a"), POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 3))] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [PBool False] simplified - , ignoreTest $ testCase "simpProp-concrete-or-2" $ do + assertEqualM "Must be equal" [PBool False] simplified + , ignoreTest $ test "simpProp-concrete-or-2" $ do let -- Currently does not work, because we don't do simplification inside -- POr/PAnd using canBeSat -- a = 5 && (a=4 || a=5) -> a=5 t = [PEq (Lit 5) (Var "a"), POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 5))] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [] simplified - , testCase "simpProp-concrete-and-1" $ do + assertEqualM "Must be equal" [] simplified + , test "simpProp-concrete-and-1" $ do let -- a = 5 && (a=4 && a=3) -> False t = [PEq (Lit 5) (Var "a"), PAnd (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 3))] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [PBool False] simplified - , testCase "simpProp-concrete-or-of-or" $ do + assertEqualM "Must be equal" [PBool False] simplified + , test "simpProp-concrete-or-of-or" $ do let -- a = 5 && ((a=4 || a=6) || a=3) -> False t = [PEq (Lit 5) (Var "a"), POr (POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 6))) (PEq (Var "a") (Lit 3))] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [PBool False] simplified - , testCase "simpProp-concrete-or-eq-rem" $ do + assertEqualM "Must be equal" [PBool False] simplified + , test "simpProp-concrete-or-eq-rem" $ do let -- a = 5 && ((a=4 || a=6) || a=3) -> False t = [PEq (Lit 5) (Var "a"), POr (POr (PEq (Var "a") (Lit 4)) (PEq (Var "a") (Lit 6))) (PEq (Var "a") (Lit 3))] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [PBool False] simplified - , testCase "simpProp-inner-expr-simp" $ do + assertEqualM "Must be equal" [PBool False] simplified + , test "simpProp-inner-expr-simp" $ do let -- 5+1 = 6 t = [PEq (Add (Lit 5) (Lit 1)) (Var "a")] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [PEq (Lit 6) (Var "a")] simplified - , testCase "simpProp-inner-expr-simp-with-canBeSat" $ do + assertEqualM "Must be equal" [PEq (Lit 6) (Var "a")] simplified + , test "simpProp-inner-expr-simp-with-canBeSat" $ do let -- 5+1 = 6, 6 != 7 t = [PAnd (PEq (Add (Lit 5) (Lit 1)) (Var "a")) (PEq (Var "a") (Lit 7))] simplified = Expr.simplifyProps t - assertEqual "Must be equal" [PBool False] simplified + assertEqualM "Must be equal" [PBool False] simplified ] , testGroup "MemoryTests" - [ testCase "read-write-same-byte" $ assertEqual "" + [ test "read-write-same-byte" $ assertEqualM "" (LitByte 0x12) (Expr.readByte (Lit 0x20) (WriteByte (Lit 0x20) (LitByte 0x12) mempty)) - , testCase "read-write-same-word" $ assertEqual "" + , test "read-write-same-word" $ assertEqualM "" (Lit 0x12) (Expr.readWord (Lit 0x20) (WriteWord (Lit 0x20) (Lit 0x12) mempty)) - , testCase "read-byte-write-word" $ assertEqual "" + , test "read-byte-write-word" $ assertEqualM "" -- reading at byte 31 a word that's been written should return LSB (LitByte 0x12) (Expr.readByte (Lit 0x1f) (WriteWord (Lit 0x0) (Lit 0x12) mempty)) - , testCase "read-byte-write-word2" $ assertEqual "" + , test "read-byte-write-word2" $ assertEqualM "" -- Same as above, but offset not 0 (LitByte 0x12) (Expr.readByte (Lit 0x20) (WriteWord (Lit 0x1) (Lit 0x12) mempty)) - ,testCase "read-write-with-offset" $ assertEqual "" + ,test "read-write-with-offset" $ assertEqualM "" -- 0x3F = 63 decimal, 0x20 = 32. 0x12 = 18 -- We write 128bits (32 Bytes), representing 18 at offset 32. -- Hence, when reading out the 63rd byte, we should read out the LSB 8 bits -- which is 0x12 (LitByte 0x12) (Expr.readByte (Lit 0x3F) (WriteWord (Lit 0x20) (Lit 0x12) mempty)) - ,testCase "read-write-with-offset2" $ assertEqual "" + ,test "read-write-with-offset2" $ assertEqualM "" -- 0x20 = 32, 0x3D = 61 -- we write 128 bits (32 Bytes) representing 0x10012, at offset 32. -- we then read out a byte at offset 61. -- So, at 63 we'd read 0x12, at 62 we'd read 0x00, at 61 we should read 0x1 (LitByte 0x1) (Expr.readByte (Lit 0x3D) (WriteWord (Lit 0x20) (Lit 0x10012) mempty)) - , testCase "read-write-with-extension-to-zero" $ assertEqual "" + , test "read-write-with-extension-to-zero" $ assertEqualM "" -- write word and read it at the same place (i.e. 0 offset) (Lit 0x12) (Expr.readWord (Lit 0x0) (WriteWord (Lit 0x0) (Lit 0x12) mempty)) - , testCase "read-write-with-extension-to-zero-with-offset" $ assertEqual "" + , test "read-write-with-extension-to-zero-with-offset" $ assertEqualM "" -- write word and read it at the same offset of 4 (Lit 0x12) (Expr.readWord (Lit 0x4) (WriteWord (Lit 0x4) (Lit 0x12) mempty)) - , testCase "read-write-with-extension-to-zero-with-offset2" $ assertEqual "" + , test "read-write-with-extension-to-zero-with-offset2" $ assertEqualM "" -- write word and read it at the same offset of 16 (Lit 0x12) (Expr.readWord (Lit 0x20) (WriteWord (Lit 0x20) (Lit 0x12) mempty)) - , testCase "read-word-copySlice-overlap" $ assertEqual "" + , test "read-word-copySlice-overlap" $ assertEqualM "" -- we should not recurse into a copySlice if the read index + 32 overlaps the sliced region (ReadWord (Lit 40) (CopySlice (Lit 0) (Lit 30) (Lit 12) (WriteWord (Lit 10) (Lit 0x64) (AbstractBuf "hi")) (AbstractBuf "hi"))) (Expr.readWord (Lit 40) (CopySlice (Lit 0) (Lit 30) (Lit 12) (WriteWord (Lit 10) (Lit 0x64) (AbstractBuf "hi")) (AbstractBuf "hi"))) - , testCase "indexword-MSB" $ assertEqual "" + , test "indexword-MSB" $ assertEqualM "" -- 31st is the LSB byte (of 32) (LitByte 0x78) (Expr.indexWord (Lit 31) (Lit 0x12345678)) - , testCase "indexword-LSB" $ assertEqual "" + , test "indexword-LSB" $ assertEqualM "" -- 0th is the MSB byte (of 32), Lit 0xff22bb... is exactly 32 Bytes. (LitByte 0xff) (Expr.indexWord (Lit 0) (Lit 0xff22bb4455667788990011223344556677889900112233445566778899001122)) - , testCase "indexword-LSB2" $ assertEqual "" + , test "indexword-LSB2" $ assertEqualM "" -- same as above, but with offset 2 (LitByte 0xbb) (Expr.indexWord (Lit 2) (Lit 0xff22bb4455667788990011223344556677889900112233445566778899001122)) - , testCase "encodeConcreteStore-overwrite" $ - assertEqual "" + , test "encodeConcreteStore-overwrite" $ + assertEqualM "" "(store (store ((as const Storage) #x0000000000000000000000000000000000000000000000000000000000000000) (_ bv1 256) (_ bv2 256)) (_ bv3 256) (_ bv4 256))" (EVM.SMT.encodeConcreteStore $ Map.fromList [(W256 1, W256 2), (W256 3, W256 4)]) - , testCase "indexword-oob-sym" $ assertEqual "" + , test "indexword-oob-sym" $ assertEqualM "" -- indexWord should return 0 for oob access (LitByte 0x0) (Expr.indexWord (Lit 100) (JoinBytes @@ -551,7 +574,7 @@ tests = testGroup "hevm" (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0) (LitByte 0))) - , testCase "stripbytes-concrete-bug" $ assertEqual "" + , test "stripbytes-concrete-bug" $ assertEqualM "" (Expr.simplifyReads (ReadByte (Lit 0) (ConcreteBuf "5"))) (LitByte 53) ] @@ -562,43 +585,42 @@ tests = testGroup "hevm" _ -> False ] , testGroup "Solidity-Expressions" - [ testCase "Trivial" $ + [ test "Trivial" $ SolidityCall "x = 3;" [] ===> AbiUInt 256 3 - - , testCase "Arithmetic" $ do + , test "Arithmetic" $ do SolidityCall "x = a + 1;" [AbiUInt 256 1] ===> AbiUInt 256 2 SolidityCall "unchecked { x = a - 1; }" [AbiUInt 8 0] ===> AbiUInt 8 255 - , testCase "keccak256()" $ + , test "keccak256()" $ SolidityCall "x = uint(keccak256(abi.encodePacked(a)));" [AbiString ""] ===> AbiUInt 256 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 , testProperty "abi encoding vs. solidity" $ withMaxSuccess 20 $ forAll (arbitrary >>= genAbiValue) $ - \y -> ioProperty $ do + \y -> prop $ do Just encoded <- runStatements [i| x = abi.encode(a);|] [y] AbiBytesDynamicType let solidityEncoded = case decodeAbiValue (AbiTupleType $ V.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of AbiTuple (V.toList -> [e]) -> e _ -> internalError "AbiTuple expected" let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [y]) - assertEqual "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded) + assertEqualM "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded) , testProperty "abi encoding vs. solidity (2 args)" $ withMaxSuccess 20 $ forAll (arbitrary >>= bothM genAbiValue) $ - \(x', y') -> ioProperty $ do + \(x', y') -> prop $ do Just encoded <- runStatements [i| x = abi.encode(a, b);|] [x', y'] AbiBytesDynamicType let solidityEncoded = case decodeAbiValue (AbiTupleType $ V.fromList [AbiBytesDynamicType]) (BS.fromStrict encoded) of AbiTuple (V.toList -> [e]) -> e _ -> internalError "AbiTuple expected" let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [x',y']) - assertEqual "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded) + assertEqualM "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded) -- we need a separate test for this because the type of a function is "function() external" in solidity but just "function" in the abi: , testProperty "abi encoding vs. solidity (function pointer)" $ withMaxSuccess 20 $ forAll (genAbiValue AbiFunctionType) $ - \y -> ioProperty $ do + \y -> prop $ do Just encoded <- runFunction [i| function foo(function() external a) public pure returns (bytes memory x) { x = abi.encode(a); @@ -608,37 +630,37 @@ tests = testGroup "hevm" AbiTuple (V.toList -> [e]) -> e _ -> internalError "AbiTuple expected" let hevmEncoded = encodeAbiValue (AbiTuple $ V.fromList [y]) - assertEqual "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded) + assertEqualM "abi encoding mismatch" solidityEncoded (AbiBytesDynamic hevmEncoded) ] , testGroup "Precompiled contracts" [ testGroup "Example (reverse)" - [ testCase "success" $ - assertEqual "example contract reverses" + [ test "success" $ + assertEqualM "example contract reverses" (execute 0xdeadbeef "foobar" 6) (Just "raboof") - , testCase "failure" $ - assertEqual "example contract fails on length mismatch" + , test "failure" $ + assertEqualM "example contract fails on length mismatch" (execute 0xdeadbeef "foobar" 5) Nothing ] , testGroup "ECRECOVER" - [ testCase "success" $ do + [ test "success" $ do let r = hex "c84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4732" s = hex "1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d" v = hex "000000000000000000000000000000000000000000000000000000000000001c" h = hex "513954cf30af6638cb8f626bd3f8c39183c26784ce826084d9d267868a18fb31" a = hex "0000000000000000000000002d5e56d45c63150d937f2182538a0f18510cb11f" - assertEqual "successful recovery" + assertEqualM "successful recovery" (Just a) (execute 1 (h <> v <> r <> s) 32) - , testCase "fail on made up values" $ do + , test "fail on made up values" $ do let r = hex "c84e55cee2032ea541a32bf6749e10c8b9344c92061724c4e751600f886f4731" s = hex "1542b6457e91098682138856165381453b3d0acae2470286fd8c8a09914b1b5d" v = hex "000000000000000000000000000000000000000000000000000000000000001c" h = hex "513954cf30af6638cb8f626bd3f8c39183c26784ce826084d9d267868a18fb31" - assertEqual "fail because bit flip" + assertEqualM "fail because bit flip" Nothing (execute 1 (h <> v <> r <> s) 32) ] @@ -659,19 +681,19 @@ tests = testGroup "hevm" ] , testGroup "Unresolved link detection" - [ testCase "holes detected" $ do + [ test "holes detected" $ do let code' = "608060405234801561001057600080fd5b5060405161040f38038061040f83398181016040528101906100329190610172565b73__$f3cbc3eb14e5bd0705af404abcf6f741ec$__63ab5c1ffe826040518263ffffffff1660e01b81526004016100699190610217565b60206040518083038186803b15801561008157600080fd5b505af4158015610095573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100b99190610145565b50506103c2565b60006100d36100ce84610271565b61024c565b9050828152602081018484840111156100ef576100ee610362565b5b6100fa8482856102ca565b509392505050565b600081519050610111816103ab565b92915050565b600082601f83011261012c5761012b61035d565b5b815161013c8482602086016100c0565b91505092915050565b60006020828403121561015b5761015a61036c565b5b600061016984828501610102565b91505092915050565b6000602082840312156101885761018761036c565b5b600082015167ffffffffffffffff8111156101a6576101a5610367565b5b6101b284828501610117565b91505092915050565b60006101c6826102a2565b6101d081856102ad565b93506101e08185602086016102ca565b6101e981610371565b840191505092915050565b60006102016003836102ad565b915061020c82610382565b602082019050919050565b6000604082019050818103600083015261023181846101bb565b90508181036020830152610244816101f4565b905092915050565b6000610256610267565b905061026282826102fd565b919050565b6000604051905090565b600067ffffffffffffffff82111561028c5761028b61032e565b5b61029582610371565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b60008115159050919050565b60005b838110156102e85780820151818401526020810190506102cd565b838111156102f7576000848401525b50505050565b61030682610371565b810181811067ffffffffffffffff821117156103255761032461032e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f6261720000000000000000000000000000000000000000000000000000000000600082015250565b6103b4816102be565b81146103bf57600080fd5b50565b603f806103d06000396000f3fe6080604052600080fdfea26469706673582212207d03b26e43dc3d116b0021ddc9817bde3762a3b14315351f11fc4be384fd14a664736f6c63430008060033" - assertBool "linker hole not detected" (containsLinkerHole code'), - testCase "no false positives" $ do + assertBoolM "linker hole not detected" (containsLinkerHole code'), + test "no false positives" $ do let code' = "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fea265627a7a723158205d775f914dcb471365a430b5f5b2cfe819e615cbbb5b2f1ccc7da1fd802e43c364736f6c634300050b0032" - assertBool "false positive" (not . containsLinkerHole $ code') + assertBoolM "false positive" (not . containsLinkerHole $ code') ] , testGroup "metadata stripper" - [ testCase "it strips the metadata for solc => 0.6" $ do + [ test "it strips the metadata for solc => 0.6" $ do let code' = hexText "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fea265627a7a723158205d775f914dcb471365a430b5f5b2cfe819e615cbbb5b2f1ccc7da1fd802e43c364736f6c634300050b0032" stripped = stripBytecodeMetadata code' - assertEqual "failed to strip metadata" (show (ByteStringS stripped)) "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fe" + assertEqualM "failed to strip metadata" (show (ByteStringS stripped)) "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806317bf8bac1461003b578063acffee6b1461005d575b600080fd5b610043610067565b604051808215151515815260200191505060405180910390f35b610065610073565b005b60008060015414905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663f8a8fd6d6040518163ffffffff1660e01b815260040160206040518083038186803b1580156100da57600080fd5b505afa1580156100ee573d6000803e3d6000fd5b505050506040513d602081101561010457600080fd5b810190808051906020019092919050505060018190555056fe" , testCase "it strips the metadata and constructor args" $ do let srccode = @@ -703,7 +725,7 @@ tests = testGroup "hevm" ] , testGroup "Panic code tests via symbolic execution" [ - testCase "assert-fail" $ do + test "assert-fail" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -713,10 +735,10 @@ tests = testGroup "hevm" } |] (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x01] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - assertEqual "Must be 0" 0 $ getVar ctr "arg1" - putStrLn $ "expected counterexample found, and it's correct: " <> (show $ getVar ctr "arg1") + assertEqualM "Must be 0" 0 $ getVar ctr "arg1" + putStrLnM $ "expected counterexample found, and it's correct: " <> (show $ getVar ctr "arg1") , - testCase "safeAdd-fail" $ do + test "safeAdd-fail" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -730,10 +752,10 @@ tests = testGroup "hevm" let y = getVar ctr "arg2" let maxUint = 2 ^ (256 :: Integer) :: Integer - assertBool "Overflow must occur" (toInteger x + toInteger y >= maxUint) - putStrLn "expected counterexample found" + assertBoolM "Overflow must occur" (toInteger x + toInteger y >= maxUint) + putStrLnM "expected counterexample found" , - testCase "div-by-zero-fail" $ do + test "div-by-zero-fail" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -743,10 +765,10 @@ tests = testGroup "hevm" } |] (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x12] c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - assertEqual "Division by 0 needs b=0" (getVar ctr "arg2") 0 - putStrLn "expected counterexample found" + assertEqualM "Division by 0 needs b=0" (getVar ctr "arg2") 0 + putStrLnM "expected counterexample found" , - testCase "unused-args-fail" $ do + test "unused-args-fail" $ do Just c <- solcRuntime "C" [i| contract C { @@ -756,9 +778,9 @@ tests = testGroup "hevm" } |] (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x1] c Nothing [] defaultVeriOpts - putStrLn "expected counterexample found" + putStrLnM "expected counterexample found" , - testCase "enum-conversion-fail" $ do + test "enum-conversion-fail" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -769,13 +791,13 @@ tests = testGroup "hevm" } |] (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x21] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - assertBool "Enum is only defined for 0 and 1" $ (getVar ctr "arg1") > 1 - putStrLn "expected counterexample found" + assertBoolM "Enum is only defined for 0 and 1" $ (getVar ctr "arg1") > 1 + putStrLnM "expected counterexample found" , -- TODO 0x22 is missing: "0x22: If you access a storage byte array that is incorrectly encoded." -- TODO below should NOT fail -- TODO this has a loop that depends on a symbolic value and currently causes interpret to loop - ignoreTest $ testCase "pop-empty-array" $ do + ignoreTest $ test "pop-empty-array" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -790,11 +812,12 @@ tests = testGroup "hevm" } |] a <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x31] c (Just (Sig "fun(uint8)" [AbiUIntType 8])) [] defaultVeriOpts - print $ length a - print $ show a - putStrLn "expected counterexample found" + liftIO $ do + print $ length a + print $ show a + putStrLnM "expected counterexample found" , - testCase "access-out-of-bounds-array" $ do + test "access-out-of-bounds-array" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -807,11 +830,11 @@ tests = testGroup "hevm" } |] (_, [Cex (_, _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x32] c (Just (Sig "fun(uint8)" [AbiUIntType 8])) [] defaultVeriOpts - -- assertBool "Access must be beyond element 2" $ (getVar ctr "arg1") > 1 - putStrLn "expected counterexample found" + -- assertBoolM "Access must be beyond element 2" $ (getVar ctr "arg1") > 1 + putStrLnM "expected counterexample found" , -- Note: we catch the assertion here, even though we are only able to explore partially - testCase "alloc-too-much" $ do + test "alloc-too-much" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -822,9 +845,9 @@ tests = testGroup "hevm" |] (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x41] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "expected counterexample found" + putStrLnM "expected counterexample found" , - testCase "vm.deal unknown address" $ do + test "vm.deal unknown address" $ do Just c <- solcRuntime "C" [i| interface Vm { @@ -840,9 +863,9 @@ tests = testGroup "hevm" } |] Right e <- reachableUserAsserts c (Just $ Sig "f(address,uint256)" [AbiAddressType, AbiUIntType 256]) - assertBool "The expression is not partial" $ Expr.containsNode isPartial e + assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e , - testCase "vm.prank underflow" $ do + test "vm.prank underflow" $ do Just c <- solcRuntime "C" [i| interface Vm { @@ -866,9 +889,9 @@ tests = testGroup "hevm" } |] r <- allBranchesFail c Nothing - assertBool "all branches must fail" (isRight r) + assertBoolM "all branches must fail" (isRight r) , - testCase "call ffi when disabled" $ do + test "call ffi when disabled" $ do Just c <- solcRuntime "C" [i| interface Vm { @@ -888,11 +911,11 @@ tests = testGroup "hevm" } |] Right e <- reachableUserAsserts c Nothing - T.putStrLn $ formatExpr e - assertBool "The expression is not partial" $ Expr.containsNode isPartial e + liftIO $ T.putStrLn $ formatExpr e + assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e , -- TODO: we can't deal with symbolic jump conditions - expectFail $ testCase "call-zero-inited-var-thats-a-function" $ do + expectFail $ test "call-zero-inited-var-thats-a-function" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -911,8 +934,8 @@ tests = testGroup "hevm" (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x51] c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts let a = fromJust $ Map.lookup (Var "arg1") cex.vars - assertEqual "unexpected cex value" a 44 - putStrLn "expected counterexample found" + assertEqualM "unexpected cex value" a 44 + putStrLnM "expected counterexample found" ] , testGroup "Symbolic-Constructor-Args" -- this produced some hard to debug failures. keeping it around since it seemed to exercise the contract creation code in interesting ways... @@ -999,10 +1022,10 @@ tests = testGroup "hevm" _ -> assertBool "did not encounter expected partial node" False ] , testGroup "Dapp-Tests" - [ testCase "Trivial-Pass" $ do + [ test "Trivial-Pass" $ do let testFile = "test/contracts/pass/trivial.sol" - runSolidityTest testFile ".*" >>= assertEqual "test result" True - , testCase "DappTools" $ do + runSolidityTest testFile ".*" >>= assertEqualM "test result" True + , test "DappTools" $ do -- quick smokecheck to make sure that we can parse dapptools style build outputs let cases = [ ("test/contracts/pass/trivial.sol", ".*", True) @@ -1013,44 +1036,44 @@ tests = testGroup "hevm" results <- forM cases $ \(testFile, match, expected) -> do actual <- runSolidityTestCustom testFile match Nothing Nothing False Nothing DappTools pure (actual == expected) - assertBool "test result" (and results) - , testCase "Trivial-Fail" $ do + assertBoolM "test result" (and results) + , test "Trivial-Fail" $ do let testFile = "test/contracts/fail/trivial.sol" - runSolidityTest testFile "prove_false" >>= assertEqual "test result" False - , testCase "Abstract" $ do + runSolidityTest testFile "prove_false" >>= assertEqualM "test result" False + , test "Abstract" $ do let testFile = "test/contracts/pass/abstract.sol" - runSolidityTest testFile ".*" >>= assertEqual "test result" True - , testCase "Constantinople" $ do + runSolidityTest testFile ".*" >>= assertEqualM "test result" True + , test "Constantinople" $ do let testFile = "test/contracts/pass/constantinople.sol" - runSolidityTest testFile ".*" >>= assertEqual "test result" True - , testCase "Prove-Tests-Pass" $ do + runSolidityTest testFile ".*" >>= assertEqualM "test result" True + , test "Prove-Tests-Pass" $ do let testFile = "test/contracts/pass/dsProvePass.sol" - runSolidityTest testFile ".*" >>= assertEqual "test result" True - , testCase "prefix-check-for-dapp" $ do + runSolidityTest testFile ".*" >>= assertEqualM "test result" True + , test "prefix-check-for-dapp" $ do let testFile = "test/contracts/fail/check-prefix.sol" - runSolidityTest testFile "check_trivial" >>= assertEqual "test result" False - , testCase "Prove-Tests-Fail" $ do + runSolidityTest testFile "check_trivial" >>= assertEqualM "test result" False + , test "Prove-Tests-Fail" $ do let testFile = "test/contracts/fail/dsProveFail.sol" - runSolidityTest testFile "prove_trivial" >>= assertEqual "test result" False - runSolidityTest testFile "prove_trivial_dstest" >>= assertEqual "test result" False - runSolidityTest testFile "prove_add" >>= assertEqual "test result" False - runSolidityTestCustom testFile "prove_smtTimeout" (Just 1) Nothing False Nothing Foundry >>= assertEqual "test result" False - runSolidityTest testFile "prove_multi" >>= assertEqual "test result" False + runSolidityTest testFile "prove_trivial" >>= assertEqualM "test result" False + runSolidityTest testFile "prove_trivial_dstest" >>= assertEqualM "test result" False + runSolidityTest testFile "prove_add" >>= assertEqualM "test result" False + runSolidityTestCustom testFile "prove_smtTimeout" (Just 1) Nothing False Nothing Foundry >>= assertEqualM "test result" False + runSolidityTest testFile "prove_multi" >>= assertEqualM "test result" False -- TODO: implement overflow checking optimizations and enable, currently this runs forever - --runSolidityTest testFile "prove_distributivity" >>= assertEqual "test result" False - , testCase "Loop-Tests" $ do + --runSolidityTest testFile "prove_distributivity" >>= assertEqualM "test result" False + , test "Loop-Tests" $ do let testFile = "test/contracts/pass/loops.sol" - runSolidityTestCustom testFile "prove_loop" Nothing (Just 10) False Nothing Foundry >>= assertEqual "test result" True - runSolidityTestCustom testFile "prove_loop" Nothing (Just 100) False Nothing Foundry >>= assertEqual "test result" False - , testCase "Cheat-Codes-Pass" $ do + runSolidityTestCustom testFile "prove_loop" Nothing (Just 10) False Nothing Foundry >>= assertEqualM "test result" True + runSolidityTestCustom testFile "prove_loop" Nothing (Just 100) False Nothing Foundry >>= assertEqualM "test result" False + , test "Cheat-Codes-Pass" $ do let testFile = "test/contracts/pass/cheatCodes.sol" - runSolidityTest testFile ".*" >>= assertEqual "test result" True - , testCase "Unwind" $ do + runSolidityTest testFile ".*" >>= assertEqualM "test result" True + , test "Unwind" $ do let testFile = "test/contracts/pass/unwind.sol" - runSolidityTest testFile ".*" >>= assertEqual "test result" True + runSolidityTest testFile ".*" >>= assertEqualM "test result" True ] , testGroup "max-iterations" - [ testCase "concrete-loops-reached" $ do + [ test "concrete-loops-reached" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1065,8 +1088,8 @@ tests = testGroup "hevm" opts = defaultVeriOpts{ maxIter = Just 3 } (e, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c sig [] opts - assertBool "The expression is not partial" $ isPartial e - , testCase "concrete-loops-not-reached" $ do + assertBoolM "The expression is not partial" $ isPartial e + , test "concrete-loops-not-reached" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1082,8 +1105,8 @@ tests = testGroup "hevm" opts = defaultVeriOpts{ maxIter = Just 6 } (e, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c sig [] opts - assertBool "The expression is partial" $ not $ isPartial e - , testCase "symbolic-loops-reached" $ do + assertBoolM "The expression is partial" $ not $ isPartial e + , test "symbolic-loops-reached" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1096,8 +1119,8 @@ tests = testGroup "hevm" |] (e, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] (defaultVeriOpts{ maxIter = Just 5 }) - assertBool "The expression is not partial" $ Expr.containsNode isPartial e - , testCase "inconsistent-paths" $ do + assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e + , test "inconsistent-paths" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1116,8 +1139,8 @@ tests = testGroup "hevm" opts = defaultVeriOpts{ maxIter = Just 10, askSmtIters = 5 } (e, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c sig [] opts - assertBool "The expression is not partial" $ Expr.containsNode isPartial e - , testCase "symbolic-loops-not-reached" $ do + assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e + , test "symbolic-loops-not-reached" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1135,10 +1158,10 @@ tests = testGroup "hevm" opts = defaultVeriOpts{ maxIter = Just 5, askSmtIters = 1 } (e, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c sig [] opts - assertBool "The expression is partial" $ not (Expr.containsNode isPartial e) + assertBoolM "The expression is partial" $ not (Expr.containsNode isPartial e) ] , testGroup "Symbolic Addresses" - [ testCase "symbolic-address-create" $ do + [ test "symbolic-address-create" $ do let src = [i| contract A { constructor() payable {} @@ -1163,11 +1186,11 @@ tests = testGroup "hevm" let code = case ca.code of RuntimeCode (ConcreteRuntimeCode c') -> c' _ -> internalError "expected concrete code" - assertEqual "balance mismatch" (Var "arg1") ca.balance - assertEqual "code mismatch" (stripBytecodeMetadata a) (stripBytecodeMetadata code) - assertEqual "nonce mismatch" (Just 1) ca.nonce - _ -> assertBool "too many success nodes!" False - , testCase "symbolic-balance-call" $ do + assertEqualM "balance mismatch" (Var "arg1") ca.balance + assertEqualM "code mismatch" (stripBytecodeMetadata a) (stripBytecodeMetadata code) + assertEqualM "nonce mismatch" (Just 1) ca.nonce + _ -> assertBoolM "too many success nodes!" False + , test "symbolic-balance-call" $ do let src = [i| contract A { function f() public payable returns (uint) { @@ -1185,9 +1208,9 @@ tests = testGroup "hevm" |] Just c <- solcRuntime "C" src res <- reachableUserAsserts c Nothing - assertBool "unexpected cex" (isRight res) + assertBoolM "unexpected cex" (isRight res) -- TODO: implement missing aliasing rules - , expectFail $ testCase "deployed-contract-addresses-cannot-alias" $ do + , expectFail $ test "deployed-contract-addresses-cannot-alias" $ do Just c <- solcRuntime "C" [i| contract A {} @@ -1199,8 +1222,8 @@ tests = testGroup "hevm" } |] res <- reachableUserAsserts c Nothing - assertBool "should not be able to alias" (isRight res) - , testCase "addresses-in-args-can-alias-anything" $ do + assertBoolM "should not be able to alias" (isRight res) + , test "addresses-in-args-can-alias-anything" $ do let addrs :: [Text] addrs = ["address(this)", "tx.origin", "block.coinbase", "msg.sender"] sig = Just $ Sig "f(address)" [AbiAddressType] @@ -1217,12 +1240,13 @@ tests = testGroup "hevm" Left [cex] <- reachableUserAsserts c sig pure cex.addrs - let check as a = (Map.lookup (SymAddr "arg1") as) @?= (Map.lookup a as) - check self (SymAddr "entrypoint") - check origin (SymAddr "origin") - check coinbase (SymAddr "coinbase") - check caller (SymAddr "caller") - , testCase "addresses-in-args-can-alias-themselves" $ do + liftIO $ do + let check as a = (Map.lookup (SymAddr "arg1") as) @?= (Map.lookup a as) + check self (SymAddr "entrypoint") + check origin (SymAddr "origin") + check coinbase (SymAddr "coinbase") + check caller (SymAddr "caller") + , test "addresses-in-args-can-alias-themselves" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1235,9 +1259,9 @@ tests = testGroup "hevm" Left [cex] <- reachableUserAsserts c sig let arg1 = fromJust $ Map.lookup (SymAddr "arg1") cex.addrs arg2 = fromJust $ Map.lookup (SymAddr "arg1") cex.addrs - assertEqual "should match" arg1 arg2 + assertEqualM "should match" arg1 arg2 -- TODO: fails due to missing aliasing rules - , expectFail $ testCase "tx.origin cannot alias deployed contracts" $ do + , expectFail $ test "tx.origin cannot alias deployed contracts" $ do Just c <- solcRuntime "C" [i| contract A {} @@ -1249,8 +1273,8 @@ tests = testGroup "hevm" } |] cexs <- reachableUserAsserts c Nothing - assertBool "unexpected cex" (isRight cexs) - , testCase "tx.origin can alias everything else" $ do + assertBoolM "unexpected cex" (isRight cexs) + , test "tx.origin can alias everything else" $ do let addrs = ["address(this)", "block.coinbase", "msg.sender", "arg"] :: [Text] sig = Just $ Sig "f(address)" [AbiAddressType] checkVs vs = [i| @@ -1266,12 +1290,13 @@ tests = testGroup "hevm" Left [cex] <- reachableUserAsserts c sig pure cex.addrs - let check as a = (Map.lookup (SymAddr "origin") as) @?= (Map.lookup a as) - check self (SymAddr "entrypoint") - check coinbase (SymAddr "coinbase") - check caller (SymAddr "caller") - check arg (SymAddr "arg1") - , testCase "coinbase can alias anything" $ do + liftIO $ do + let check as a = (Map.lookup (SymAddr "origin") as) @?= (Map.lookup a as) + check self (SymAddr "entrypoint") + check coinbase (SymAddr "coinbase") + check caller (SymAddr "caller") + check arg (SymAddr "arg1") + , test "coinbase can alias anything" $ do let addrs = ["address(this)", "tx.origin", "msg.sender", "a", "arg"] :: [Text] sig = Just $ Sig "f(address)" [AbiAddressType] checkVs vs = [i| @@ -1289,13 +1314,14 @@ tests = testGroup "hevm" Left [cex] <- reachableUserAsserts c sig pure cex.addrs - let check as a' = (Map.lookup (SymAddr "coinbase") as) @?= (Map.lookup a' as) - check self (SymAddr "entrypoint") - check origin (SymAddr "origin") - check caller (SymAddr "caller") - check a (SymAddr "freshSymAddr1") - check arg (SymAddr "arg1") - , testCase "caller can alias anything" $ do + liftIO $ do + let check as a' = (Map.lookup (SymAddr "coinbase") as) @?= (Map.lookup a' as) + check self (SymAddr "entrypoint") + check origin (SymAddr "origin") + check caller (SymAddr "caller") + check a (SymAddr "freshSymAddr1") + check arg (SymAddr "arg1") + , test "caller can alias anything" $ do let addrs = ["address(this)", "tx.origin", "block.coinbase", "a", "arg"] :: [Text] sig = Just $ Sig "f(address)" [AbiAddressType] checkVs vs = [i| @@ -1313,13 +1339,14 @@ tests = testGroup "hevm" Left [cex] <- reachableUserAsserts c sig pure cex.addrs - let check as a' = (Map.lookup (SymAddr "caller") as) @?= (Map.lookup a' as) - check self (SymAddr "entrypoint") - check origin (SymAddr "origin") - check coinbase (SymAddr "coinbase") - check a (SymAddr "freshSymAddr1") - check arg (SymAddr "arg1") - , testCase "vm.load fails for a potentially aliased address" $ do + liftIO $ do + let check as a' = (Map.lookup (SymAddr "caller") as) @?= (Map.lookup a' as) + check self (SymAddr "entrypoint") + check origin (SymAddr "origin") + check coinbase (SymAddr "coinbase") + check a (SymAddr "freshSymAddr1") + check arg (SymAddr "arg1") + , test "vm.load fails for a potentially aliased address" $ do Just c <- solcRuntime "C" [i| interface Vm { @@ -1335,7 +1362,7 @@ tests = testGroup "hevm" (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c Nothing [] defaultVeriOpts Nothing (Just $ checkBadCheatCode "load(address,bytes32)") pure () - , testCase "vm.store fails for a potentially aliased address" $ do + , test "vm.store fails for a potentially aliased address" $ do Just c <- solcRuntime "C" [i| interface Vm { @@ -1352,7 +1379,7 @@ tests = testGroup "hevm" verifyContract s c Nothing [] defaultVeriOpts Nothing (Just $ checkBadCheatCode "store(address,bytes32,bytes32)") pure () -- TODO: make this work properly - , testCase "transfering-eth-does-not-dealias" $ do + , test "transfering-eth-does-not-dealias" $ do Just c <- solcRuntime "C" [i| // we can't do calls to unknown code yet so we use selfdestruct @@ -1382,8 +1409,8 @@ tests = testGroup "hevm" |] Right e <- reachableUserAsserts c Nothing -- TODO: this should work one day - assertBool "should be partial" (Expr.containsNode isPartial e) - , testCase "addresses-in-context-are-symbolic" $ do + assertBoolM "should be partial" (Expr.containsNode isPartial e) + , test "addresses-in-context-are-symbolic" $ do Just a <- solcRuntime "A" [i| contract A { @@ -1418,17 +1445,17 @@ tests = testGroup "hevm" |] [acex,bcex,ccex,dcex] <- forM [a,b,c,d] $ \con -> do Left [cex] <- reachableUserAsserts con Nothing - assertEqual "wrong number of addresses" 1 (length (Map.keys cex.addrs)) + assertEqualM "wrong number of addresses" 1 (length (Map.keys cex.addrs)) pure cex - assertEqual "wrong model for a" (Addr 0) (fromJust $ Map.lookup (SymAddr "caller") acex.addrs) - assertEqual "wrong model for b" (Addr 1) (fromJust $ Map.lookup (SymAddr "coinbase") bcex.addrs) - assertEqual "wrong model for c" (Addr 2) (fromJust $ Map.lookup (SymAddr "origin") ccex.addrs) - assertEqual "wrong model for d" (Addr 3) (fromJust $ Map.lookup (SymAddr "entrypoint") dcex.addrs) + assertEqualM "wrong model for a" (Addr 0) (fromJust $ Map.lookup (SymAddr "caller") acex.addrs) + assertEqualM "wrong model for b" (Addr 1) (fromJust $ Map.lookup (SymAddr "coinbase") bcex.addrs) + assertEqualM "wrong model for c" (Addr 2) (fromJust $ Map.lookup (SymAddr "origin") ccex.addrs) + assertEqualM "wrong model for d" (Addr 3) (fromJust $ Map.lookup (SymAddr "entrypoint") dcex.addrs) ] , testGroup "Symbolic execution" [ - testCase "require-test" $ do + test "require-test" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1439,9 +1466,10 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256)" [AbiIntType 256])) [] defaultVeriOpts - putStrLn "Require works as expected" + putStrLnM "Require works as expected" , - testCase "ITE-with-bitwise-AND" $ do + -- here test + test "ITE-with-bitwise-AND" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1459,9 +1487,9 @@ tests = testGroup "hevm" |] -- should find a counterexample (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "expected counterexample found" + putStrLnM "expected counterexample found" , - testCase "ITE-with-bitwise-OR" $ do + test "ITE-with-bitwise-OR" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1477,14 +1505,14 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "this should always be true, due to bitwise OR with positive value" + putStrLnM "this should always be true, due to bitwise OR with positive value" , -- CopySlice check -- uses identity precompiled contract (0x4) to copy memory -- checks 9af114613075a2cd350633940475f8b6699064de (readByte + CopySlice had src/dest mixed up) -- without 9af114613 it dies with: `Exception: UnexpectedSymbolicArg 296 "MSTORE index"` -- TODO: check 9e734b9da90e3e0765128b1f20ce1371f3a66085 (bufLength + copySlice was off by 1) - testCase "copyslice-check" $ do + test "copyslice-check" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1505,10 +1533,10 @@ tests = testGroup "hevm" let sig = Just (Sig "checkval(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256]) (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c sig [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , -- TODO look at tests here for SAR: https://github.com/dapphub/dapptools/blob/01ef8ea418c3fe49089a44d56013d8fcc34a1ec2/src/dapp-tests/pass/constantinople.sol#L250 - testCase "opcode-sar-neg" $ do + test "opcode-sar-neg" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1523,9 +1551,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts - putStrLn "SAR works as expected" + putStrLnM "SAR works as expected" , - testCase "opcode-sar-pos" $ do + test "opcode-sar-pos" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1540,9 +1568,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts - putStrLn "SAR works as expected" + putStrLnM "SAR works as expected" , - testCase "opcode-sar-fixedval-pos" $ do + test "opcode-sar-fixedval-pos" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1557,9 +1585,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts - putStrLn "SAR works as expected" + putStrLnM "SAR works as expected" , - testCase "opcode-sar-fixedval-neg" $ do + test "opcode-sar-fixedval-neg" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1574,9 +1602,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(int256,int256)" [AbiIntType 256, AbiIntType 256])) [] defaultVeriOpts - putStrLn "SAR works as expected" + putStrLnM "SAR works as expected" , - testCase "opcode-div-zero-1" $ do + test "opcode-div-zero-1" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1591,9 +1619,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "sdiv works as expected" + putStrLnM "sdiv works as expected" , - testCase "opcode-sdiv-zero-1" $ do + test "opcode-sdiv-zero-1" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1608,9 +1636,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "sdiv works as expected" + putStrLnM "sdiv works as expected" , - testCase "opcode-sdiv-zero-2" $ do + test "opcode-sdiv-zero-2" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1625,9 +1653,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers CVC5 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "sdiv works as expected" + putStrLnM "sdiv works as expected" , - testCase "signed-overflow-checks" $ do + test "signed-overflow-checks" $ do Just c <- solcRuntime "C" [i| contract C { @@ -1637,9 +1665,9 @@ tests = testGroup "hevm" } |] (_, [Cex (_, _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s [0x11] c (Just (Sig "fun(int256)" [AbiIntType 256])) [] defaultVeriOpts - putStrLn "expected cex discovered" + putStrLnM "expected cex discovered" , - testCase "opcode-signextend-neg" $ do + test "opcode-signextend-neg" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1659,9 +1687,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "signextend works as expected" + putStrLnM "signextend works as expected" , - testCase "opcode-signextend-pos-nochop" $ do + test "opcode-signextend-pos-nochop" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1677,9 +1705,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts - putStrLn "signextend works as expected" + putStrLnM "signextend works as expected" , - testCase "opcode-signextend-pos-chopped" $ do + test "opcode-signextend-pos-chopped" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1695,10 +1723,10 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts - putStrLn "signextend works as expected" + putStrLnM "signextend works as expected" , -- when b is too large, value is unchanged - testCase "opcode-signextend-pos-b-toolarge" $ do + test "opcode-signextend-pos-b-toolarge" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1713,9 +1741,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint8)" [AbiUIntType 256, AbiUIntType 8])) [] defaultVeriOpts - putStrLn "signextend works as expected" + putStrLnM "signextend works as expected" , - testCase "opcode-shl" $ do + test "opcode-shl" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1731,9 +1759,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - putStrLn "SAR works as expected" + putStrLnM "SAR works as expected" , - testCase "opcode-xor-cancel" $ do + test "opcode-xor-cancel" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1748,9 +1776,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - putStrLn "XOR works as expected" + putStrLnM "XOR works as expected" , - testCase "opcode-xor-reimplement" $ do + test "opcode-xor-reimplement" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1764,9 +1792,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - putStrLn "XOR works as expected" + putStrLnM "XOR works as expected" , - testCase "opcode-add-commutative" $ do + test "opcode-add-commutative" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1786,14 +1814,14 @@ tests = testGroup "hevm" (_, [Cex (_, ctr)]) -> do let x = getVar ctr "arg1" let y = getVar ctr "arg2" - putStrLn $ "y:" <> show y - putStrLn $ "x:" <> show x - assertEqual "Addition is not commutative... that's wrong" False True + putStrLnM $ "y:" <> show y + putStrLnM $ "x:" <> show x + assertEqualM "Addition is not commutative... that's wrong" False True (_, [Qed _]) -> do - putStrLn "adding is commutative" + putStrLnM "adding is commutative" _ -> internalError "Unexpected" , - testCase "opcode-div-res-zero-on-div-by-zero" $ do + test "opcode-div-res-zero-on-div-by-zero" $ do Just c <- solcRuntime "MyContract" [i| contract MyContract { @@ -1807,12 +1835,12 @@ tests = testGroup "hevm" } } |] - (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint16)" [AbiUIntType 16])) [] debugVeriOpts - putStrLn "DIV by zero is zero" + (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint16)" [AbiUIntType 16])) [] defaultVeriOpts + putStrLnM "DIV by zero is zero" , -- Somewhat tautological since we are asserting the precondition -- on the same form as the actual "requires" clause. - testCase "SafeAdd success case" $ do + test "SafeAdd success case" $ do Just safeAdd <- solcRuntime "SafeAdd" [i| contract SafeAdd { @@ -1837,10 +1865,10 @@ tests = testGroup "hevm" sig = Just (Sig "add(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256]) (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s safeAdd sig [] defaultVeriOpts (Just pre) (Just post) - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "x == y => x + y == 2 * y" $ do + test "x == y => x + y == 2 * y" $ do Just safeAdd <- solcRuntime "SafeAdd" [i| contract SafeAdd { @@ -1864,9 +1892,9 @@ tests = testGroup "hevm" _ -> PBool True (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s safeAdd (Just (Sig "add(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts (Just pre) (Just post) - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "summary storage writes" $ do + test "summary storage writes" $ do Just c <- solcRuntime "A" [i| contract A { @@ -1895,11 +1923,11 @@ tests = testGroup "hevm" sig = Just (Sig "f(uint256)" [AbiUIntType 256]) (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post) - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , -- tests how whiffValue handles Neg via application of the triple IsZero simplification rule -- regression test for: https://github.com/dapphub/dapptools/pull/698 - testCase "Neg" $ do + test "Neg" $ do let src = [i| object "Neg" { @@ -1918,11 +1946,11 @@ tests = testGroup "hevm" } } |] - Just c <- yulRuntime "Neg" src + Just c <- liftIO $ yulRuntime "Neg" src (res, [Qed _]) <- withSolvers Z3 4 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "hello(address)" [AbiAddressType])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "catch-storage-collisions-noproblem" $ do + test "catch-storage-collisions-noproblem" $ do Just c <- solcRuntime "A" [i| contract A { @@ -1957,11 +1985,11 @@ tests = testGroup "hevm" sig = Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256]) (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post) - putStrLn "Correct, this can never fail" + putStrLnM "Correct, this can never fail" , -- Inspired by these `msg.sender == to` token bugs -- which break linearity of totalSupply. - testCase "catch-storage-collisions-good" $ do + test "catch-storage-collisions-good" $ do Just c <- solcRuntime "A" [i| contract A { @@ -1996,12 +2024,12 @@ tests = testGroup "hevm" verifyContract s c sig [] defaultVeriOpts (Just pre) (Just post) let x = getVar ctr "arg1" let y = getVar ctr "arg2" - putStrLn $ "y:" <> show y - putStrLn $ "x:" <> show x - assertEqual "Catch storage collisions" x y - putStrLn "expected counterexample found" + putStrLnM $ "y:" <> show y + putStrLnM $ "x:" <> show x + assertEqualM "Catch storage collisions" x y + putStrLnM "expected counterexample found" , - testCase "simple-assert" $ do + test "simple-assert" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2011,9 +2039,9 @@ tests = testGroup "hevm" } |] (_, [Cex (Failure _ _ (Revert msg), _)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo()" [])) [] defaultVeriOpts - assertEqual "incorrect revert msg" msg (ConcreteBuf $ panicMsg 0x01) + assertEqualM "incorrect revert msg" msg (ConcreteBuf $ panicMsg 0x01) , - testCase "simple-assert-2" $ do + test "simple-assert-2" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2023,10 +2051,10 @@ tests = testGroup "hevm" } |] (_, [(Cex (_, ctr))]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - assertEqual "Must be 10" 10 $ getVar ctr "arg1" - putStrLn "Got 10 Cex, as expected" + assertEqualM "Must be 10" 10 $ getVar ctr "arg1" + putStrLnM "Got 10 Cex, as expected" , - testCase "assert-fail-equal" $ do + test "assert-fail-equal" $ do Just c <- solcRuntime "AssertFailEqual" [i| contract AssertFailEqual { @@ -2036,12 +2064,12 @@ tests = testGroup "hevm" } } |] - (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] debugVeriOpts + (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts let ints = map (flip getVar "arg1") [a,b] - assertBool "0 must be one of the Cex-es" $ isJust $ List.elemIndex 0 ints - putStrLn "expected 2 counterexamples found, one Cex is the 0 value" + assertBoolM "0 must be one of the Cex-es" $ isJust $ List.elemIndex 0 ints + putStrLnM "expected 2 counterexamples found, one Cex is the 0 value" , - testCase "assert-fail-notequal" $ do + test "assert-fail-notequal" $ do Just c <- solcRuntime "AssertFailNotEqual" [i| contract AssertFailNotEqual { @@ -2054,10 +2082,10 @@ tests = testGroup "hevm" (_, [Cex (_, a), Cex (_, b)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] defaultVeriOpts let x = getVar a "arg1" let y = getVar b "arg1" - assertBool "At least one has to be 0, to go through the first assert" (x == 0 || y == 0) - putStrLn "expected 2 counterexamples found." + assertBoolM "At least one has to be 0, to go through the first assert" (x == 0 || y == 0) + putStrLnM "expected 2 counterexamples found." , - testCase "assert-fail-twoargs" $ do + test "assert-fail-twoargs" $ do Just c <- solcRuntime "AssertFailTwoParams" [i| contract AssertFailTwoParams { @@ -2068,9 +2096,9 @@ tests = testGroup "hevm" } |] (_, [Cex _, Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - putStrLn "expected 2 counterexamples found" + putStrLnM "expected 2 counterexamples found" , - testCase "assert-2nd-arg" $ do + test "assert-2nd-arg" $ do Just c <- solcRuntime "AssertFailTwoParams" [i| contract AssertFailTwoParams { @@ -2080,11 +2108,11 @@ tests = testGroup "hevm" } |] (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - assertEqual "Must be 666" 666 $ getVar ctr "arg2" - putStrLn "Found arg2 Ctx to be 666" + assertEqualM "Must be 666" 666 $ getVar ctr "arg2" + putStrLnM "Found arg2 Ctx to be 666" , -- LSB is zeroed out, byte(31,x) takes LSB, so y==0 always holds - testCase "check-lsb-msb1" $ do + test "check-lsb-msb1" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2097,11 +2125,11 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , -- We zero out everything but the LSB byte. However, byte(31,x) takes the LSB byte -- so there is a counterexamle, where LSB of x is not zero - testCase "check-lsb-msb2" $ do + test "check-lsb-msb2" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2114,12 +2142,12 @@ tests = testGroup "hevm" } |] (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - assertBool "last byte must be non-zero" $ ((Data.Bits..&.) (getVar ctr "arg1") 0xff) > 0 - putStrLn "Expected counterexample found" + assertBoolM "last byte must be non-zero" $ ((Data.Bits..&.) (getVar ctr "arg1") 0xff) > 0 + putStrLnM "Expected counterexample found" , -- We zero out everything but the 2nd LSB byte. However, byte(31,x) takes the 2nd LSB byte -- so there is a counterexamle, where 2nd LSB of x is not zero - testCase "check-lsb-msb3 -- 2nd byte" $ do + test "check-lsb-msb3 -- 2nd byte" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2132,11 +2160,11 @@ tests = testGroup "hevm" } |] (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - assertBool "second to last byte must be non-zero" $ ((Data.Bits..&.) (getVar ctr "arg1") 0xff00) > 0 - putStrLn "Expected counterexample found" + assertBoolM "second to last byte must be non-zero" $ ((Data.Bits..&.) (getVar ctr "arg1") 0xff00) > 0 + putStrLnM "Expected counterexample found" , -- Reverse of thest above - testCase "check-lsb-msb4 2nd byte rev" $ do + test "check-lsb-msb4 2nd byte rev" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2151,10 +2179,10 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , -- Bitwise OR operation test - testCase "opcode-bitwise-or-full-1s" $ do + test "opcode-bitwise-or-full-1s" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2167,10 +2195,10 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "When OR-ing with full 1's we should get back full 1's" + putStrLnM "When OR-ing with full 1's we should get back full 1's" , -- Bitwise OR operation test - testCase "opcode-bitwise-or-byte-of-1s" $ do + test "opcode-bitwise-or-byte-of-1s" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2184,9 +2212,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "When OR-ing with a byte of 1's, we should get 1's back there" + putStrLnM "When OR-ing with a byte of 1's, we should get 1's back there" , - testCase "Deposit contract loop (z3)" $ do + test "Deposit contract loop (z3)" $ do Just c <- solcRuntime "Deposit" [i| contract Deposit { @@ -2206,9 +2234,9 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "deposit(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "Deposit-contract-loop-error-version" $ do + test "Deposit-contract-loop-error-version" $ do Just c <- solcRuntime "Deposit" [i| contract Deposit { @@ -2228,10 +2256,10 @@ tests = testGroup "hevm" } |] (_, [Cex (_, ctr)]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s allPanicCodes c (Just (Sig "deposit(uint8)" [AbiUIntType 8])) [] defaultVeriOpts - assertEqual "Must be 255" 255 $ getVar ctr "arg1" - putStrLn $ "expected counterexample found, and it's correct: " <> (show $ getVar ctr "arg1") + assertEqualM "Must be 255" 255 $ getVar ctr "arg1" + putStrLnM $ "expected counterexample found, and it's correct: " <> (show $ getVar ctr "arg1") , - testCase "explore function dispatch" $ do + test "explore function dispatch" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2241,9 +2269,9 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "check-asm-byte-in-bounds" $ do + test "check-asm-byte-in-bounds" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2260,9 +2288,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts - putStrLn "in bounds byte reads return the expected value" + putStrLnM "in bounds byte reads return the expected value" , - testCase "check-div-mod-sdiv-smod-by-zero-constant-prop" $ do + test "check-div-mod-sdiv-smod-by-zero-constant-prop" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2290,9 +2318,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "foo(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn "div/mod/sdiv/smod by zero works as expected during constant propagation" + putStrLnM "div/mod/sdiv/smod by zero works as expected during constant propagation" , - testCase "check-asm-byte-oob" $ do + test "check-asm-byte-oob" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2305,9 +2333,9 @@ tests = testGroup "hevm" } |] (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts - putStrLn "oob byte reads always return 0" + putStrLnM "oob byte reads always return 0" , - testCase "injectivity of keccak (diff sizes)" $ do + test "injectivity of keccak (diff sizes)" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2322,7 +2350,7 @@ tests = testGroup "hevm" Right _ <- reachableUserAsserts c (Just $ Sig "f(uint128,uint256)" [AbiUIntType 128, AbiUIntType 256]) pure () , - testCase "injectivity of keccak (32 bytes)" $ do + test "injectivity of keccak (32 bytes)" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2332,9 +2360,9 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "injectivity of keccak contrapositive (32 bytes)" $ do + test "injectivity of keccak contrapositive (32 bytes)" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2345,9 +2373,9 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "injectivity of keccak (64 bytes)" $ do + test "injectivity of keccak (64 bytes)" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2361,11 +2389,11 @@ tests = testGroup "hevm" let y = getVar ctr "arg2" let w = getVar ctr "arg3" let z = getVar ctr "arg4" - assertEqual "x==y for hash collision" x y - assertEqual "w==z for hash collision" w z - putStrLn "expected counterexample found" + assertEqualM "x==y for hash collision" x y + assertEqualM "w==z for hash collision" w z + putStrLnM "expected counterexample found" , - testCase "calldata beyond calldatasize is 0 (symbolic calldata)" $ do + test "calldata beyond calldatasize is 0 (symbolic calldata)" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2380,9 +2408,9 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c Nothing [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "calldata beyond calldatasize is 0 (concrete dalldata prefix)" $ do + test "calldata beyond calldatasize is 0 (concrete dalldata prefix)" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2397,9 +2425,9 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "calldata symbolic access" $ do + test "calldata symbolic access" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2417,9 +2445,9 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "multiple-contracts" $ do + test "multiple-contracts" $ do let code = [i| contract C { @@ -2440,7 +2468,7 @@ tests = testGroup "hevm" Just c <- solcRuntime "C" code Just a <- solcRuntime "A" code (_, [Cex (_, cex)]) <- withSolvers Z3 1 Nothing $ \s -> do - vm <- stToIO $ abstractVM (mkCalldata (Just (Sig "call_A()" [])) []) c Nothing False + vm <- liftIO $ stToIO $ abstractVM (mkCalldata (Just (Sig "call_A()" [])) []) c Nothing False <&> set (#state % #callvalue) (Lit 0) <&> over (#env % #contracts) (Map.insert aAddr (initialContract (RuntimeCode (ConcreteRuntimeCode a)))) @@ -2453,10 +2481,10 @@ tests = testGroup "hevm" (Just x, Nothing) -> x /= 0 _ -> False _ -> False - assertBool "Did not find expected storage cex" testCex - putStrLn "expected counterexample found" + assertBoolM "Did not find expected storage cex" testCex + putStrLnM "expected counterexample found" , - expectFail $ testCase "calling unique contracts (read from storage)" $ do + expectFail $ test "calling unique contracts (read from storage)" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2474,9 +2502,9 @@ tests = testGroup "hevm" } |] (_, [Cex _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "call_A()" [])) [] defaultVeriOpts - putStrLn "expected counterexample found" + putStrLnM "expected counterexample found" , - testCase "keccak concrete and sym agree" $ do + test "keccak concrete and sym agree" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2488,9 +2516,9 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "kecc(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "keccak concrete and sym injectivity" $ do + test "keccak concrete and sym injectivity" $ do Just c <- solcRuntime "A" [i| contract A { @@ -2500,15 +2528,15 @@ tests = testGroup "hevm" } |] (res, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "f(uint256)" [AbiUIntType 256])) [] defaultVeriOpts - putStrLn $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" + putStrLnM $ "successfully explored: " <> show (Expr.numBranches res) <> " paths" , - testCase "safemath-distributivity-yul" $ do + test "safemath-distributivity-yul" $ do let yulsafeDistributivity = hex "6355a79a6260003560e01c14156016576015601f565b5b60006000fd60a1565b603d602d604435600435607c565b6039602435600435607c565b605d565b6052604b604435602435605d565b600435607c565b141515605a57fe5b5b565b6000828201821115151560705760006000fd5b82820190505b92915050565b6000818384048302146000841417151560955760006000fd5b82820290505b92915050565b" - vm <- stToIO $ abstractVM (mkCalldata (Just (Sig "distributivity(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) []) yulsafeDistributivity Nothing False + vm <- liftIO $ stToIO $ abstractVM (mkCalldata (Just (Sig "distributivity(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) []) yulsafeDistributivity Nothing False (_, [Qed _]) <- withSolvers Z3 1 Nothing $ \s -> verify s defaultVeriOpts vm (Just $ checkAssertions defaultPanicCodes) - putStrLn "Proven" + putStrLnM "Proven" , - testCase "safemath-distributivity-sol" $ do + test "safemath-distributivity-sol" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2531,9 +2559,9 @@ tests = testGroup "hevm" |] (_, [Qed _]) <- withSolvers CVC5 1 (Just 99999999) $ \s -> checkAssert s defaultPanicCodes c (Just (Sig "distributivity(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts - putStrLn "Proven" + putStrLnM "Proven" , - testCase "storage-cex-1" $ do + test "storage-cex-1" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2553,10 +2581,10 @@ tests = testGroup "hevm" (Just x, Just y) -> x /= y _ -> False _ -> False - assertBool "Did not find expected storage cex" testCex - putStrLn "Expected counterexample found" + assertBoolM "Did not find expected storage cex" testCex + putStrLnM "Expected counterexample found" , - testCase "storage-cex-2" $ do + test "storage-cex-2" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2577,10 +2605,10 @@ tests = testGroup "hevm" (Just x, Just y) -> x >= y _ -> False _ -> False - assertBool "Did not find expected storage cex" testCex - putStrLn "Expected counterexample found" + assertBoolM "Did not find expected storage cex" testCex + putStrLnM "Expected counterexample found" , - testCase "storage-cex-concrete" $ do + test "storage-cex-concrete" $ do Just c <- solcRuntime "C" [i| contract C { @@ -2602,51 +2630,51 @@ tests = testGroup "hevm" (Just x, Just y) -> x == y _ -> False _ -> False - assertBool "Did not find expected storage cex" testCex - putStrLn "Expected counterexample found" + assertBoolM "Did not find expected storage cex" testCex + putStrLnM "Expected counterexample found" ] , testGroup "simplification-working" [ - testCase "prop-simp-bool1" $ do + test "prop-simp-bool1" $ do let a = successGen [PAnd (PBool True) (PBool False)] b = Expr.simplify a - assertEqual "Must simplify down" (successGen [PBool False]) b - , testCase "prop-simp-bool2" $ do + assertEqualM "Must simplify down" (successGen [PBool False]) b + , test "prop-simp-bool2" $ do let a = successGen [POr (PBool True) (PBool False)] b = Expr.simplify a - assertEqual "Must simplify down" (successGen []) b - , testCase "prop-simp-LT" $ do + assertEqualM "Must simplify down" (successGen []) b + , test "prop-simp-LT" $ do let a = successGen [PLT (Lit 1) (Lit 2)] b = Expr.simplify a - assertEqual "Must simplify down" (successGen []) b - , testCase "prop-simp-GEq" $ do + assertEqualM "Must simplify down" (successGen []) b + , test "prop-simp-GEq" $ do let a = successGen [PGEq (Lit 1) (Lit 2)] b = Expr.simplify a - assertEqual "Must simplify down" (successGen [PBool False]) b - , testCase "prop-simp-multiple" $ do + assertEqualM "Must simplify down" (successGen [PBool False]) b + , test "prop-simp-multiple" $ do let a = successGen [PBool False, PBool True] b = Expr.simplify a - assertEqual "Must simplify down" (successGen [PBool False]) b - , testCase "prop-simp-expr" $ do + assertEqualM "Must simplify down" (successGen [PBool False]) b + , test "prop-simp-expr" $ do let a = successGen [PEq (Add (Lit 1) (Lit 2)) (Sub (Lit 4) (Lit 1))] b = Expr.simplify a - assertEqual "Must simplify down" (successGen []) b - , testCase "prop-simp-impl" $ do + assertEqualM "Must simplify down" (successGen []) b + , test "prop-simp-impl" $ do let a = successGen [PImpl (PBool False) (PEq (Var "abc") (Var "bcd"))] b = Expr.simplify a - assertEqual "Must simplify down" (successGen []) b + assertEqualM "Must simplify down" (successGen []) b ] , testGroup "equivalence-checking" [ - testCase "eq-yul-simple-cex" $ do - Just aPrgm <- yul "" + test "eq-yul-simple-cex" $ do + Just aPrgm <- liftIO $ yul "" [i| { calldatacopy(0, 0, 32) @@ -2656,7 +2684,7 @@ tests = testGroup "hevm" default { invalid() } } |] - Just bPrgm <- yul "" + Just bPrgm <- liftIO $ yul "" [i| { calldatacopy(0, 0, 32) @@ -2668,9 +2696,9 @@ tests = testGroup "hevm" |] withSolvers Z3 3 Nothing $ \s -> do a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing []) - assertBool "Must have a difference" (any isCex a) + assertBoolM "Must have a difference" (any isCex a) , - testCase "eq-sol-exp-qed" $ do + test "eq-sol-exp-qed" $ do Just aPrgm <- solcRuntime "C" [i| contract C { @@ -2693,9 +2721,9 @@ tests = testGroup "hevm" |] withSolvers Z3 3 Nothing $ \s -> do a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing []) - assertEqual "Must have no difference" [Qed ()] a + assertEqualM "Must have no difference" [Qed ()] a , - testCase "eq-balance-differs" $ do + test "eq-balance-differs" $ do Just aPrgm <- solcRuntime "C" [i| contract Send { @@ -2724,10 +2752,10 @@ tests = testGroup "hevm" |] withSolvers Z3 3 Nothing $ \s -> do a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing []) - assertBool "Must differ" (all isCex a) + assertBoolM "Must differ" (all isCex a) , -- TODO: this fails because we don't check equivalence of deployed contracts - expectFail $ testCase "eq-handles-contract-deployment" $ do + expectFail $ test "eq-handles-contract-deployment" $ do Just aPrgm <- solcRuntime "B" [i| contract Send { @@ -2785,9 +2813,9 @@ tests = testGroup "hevm" |] withSolvers Z3 3 Nothing $ \s -> do a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing []) - assertBool "Must differ" (all isCex a) + assertBoolM "Must differ" (all isCex a) , - testCase "eq-unknown-addr" $ do + test "eq-unknown-addr" $ do Just aPrgm <- solcRuntime "C" [i| contract C { @@ -2809,9 +2837,9 @@ tests = testGroup "hevm" withSolvers Z3 3 Nothing $ \s -> do let cd = mkCalldata (Just (Sig "a(address,address)" [AbiAddressType, AbiAddressType])) [] a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts cd - assertEqual "Must be different" (any isCex a) True + assertEqualM "Must be different" (any isCex a) True , - testCase "eq-sol-exp-cex" $ do + test "eq-sol-exp-cex" $ do Just aPrgm <- solcRuntime "C" [i| contract C { @@ -2834,8 +2862,8 @@ tests = testGroup "hevm" |] withSolvers Z3 3 Nothing $ \s -> do a <- equivalenceCheck s aPrgm bPrgm defaultVeriOpts (mkCalldata Nothing []) - assertEqual "Must be different" (any isCex a) True - , testCase "eq-all-yul-optimization-tests" $ do + assertEqualM "Must be different" (any isCex a) True + , test "eq-all-yul-optimization-tests" $ do let opts = defaultVeriOpts{ maxIter = Just 5, askSmtIters = 20, loopHeuristic = Naive } ignoredTests = -- unbounded loop -- @@ -3041,9 +3069,9 @@ tests = testGroup "hevm" , "reasoningBasedSimplifier/signed_division.yul" -- ACTUAL bug, SDIV ] - solcRepo <- fromMaybe (internalError "cannot find solidity repo") <$> (lookupEnv "HEVM_SOLIDITY_REPO") + solcRepo <- liftIO $ fromMaybe (internalError "cannot find solidity repo") <$> (lookupEnv "HEVM_SOLIDITY_REPO") let testDir = solcRepo <> "/test/libyul/yulOptimizerTests" - dircontents <- System.Directory.listDirectory testDir + dircontents <- liftIO $ System.Directory.listDirectory testDir let fullpaths = map ((testDir ++ "/") ++) dircontents recursiveList :: [FilePath] -> [FilePath] -> IO [FilePath] @@ -3056,13 +3084,13 @@ tests = testGroup "hevm" recursiveList (ax++fs2) b False -> recursiveList ax (a:b) recursiveList [] b = pure b - files <- recursiveList fullpaths [] + files <- liftIO $ recursiveList fullpaths [] let filesFiltered = filter (\file -> not $ any (`List.isSubsequenceOf` file) ignoredTests) files -- Takes one file which follows the Solidity Yul optimizer unit tests format, -- extracts both the nonoptimized and the optimized versions, and checks equivalence. forM_ filesFiltered (\f-> do - origcont <- readFile f + origcont <- liftIO $ readFile f let onlyAfter pattern (a:ax) = if a =~ pattern then (a:ax) else onlyAfter pattern ax onlyAfter _ [] = [] @@ -3084,31 +3112,31 @@ tests = testGroup "hevm" unfiltered = lines origcont filteredASym = symbolicMem [ x | x <- unfiltered, (not $ x =~ [re|^//|]) && (not $ x =~ [re|^$|]) ] filteredBSym = symbolicMem [ replaceAll "" $ x *=~[re|^//|] | x <- onlyAfter [re|^// step:|] unfiltered, not $ x =~ [re|^$|] ] - start <- getCurrentTime - putStrLn $ "Checking file: " <> f - when opts.debug $ do - putStrLn "-------------Original Below-----------------" + start <- liftIO $ getCurrentTime + putStrLnM $ "Checking file: " <> f + when opts.debug $ liftIO $ do + putStrLnM "-------------Original Below-----------------" mapM_ putStrLn unfiltered - putStrLn "------------- Filtered A + Symb below-----------------" + putStrLnM "------------- Filtered A + Symb below-----------------" mapM_ putStrLn filteredASym - putStrLn "------------- Filtered B + Symb below-----------------" + putStrLnM "------------- Filtered B + Symb below-----------------" mapM_ putStrLn filteredBSym - putStrLn "------------- END -----------------" - Just aPrgm <- yul "" $ T.pack $ unlines filteredASym - Just bPrgm <- yul "" $ T.pack $ unlines filteredBSym - procs <- getNumProcessors + putStrLnM "------------- END -----------------" + Just aPrgm <- liftIO $ yul "" $ T.pack $ unlines filteredASym + Just bPrgm <- liftIO $ yul "" $ T.pack $ unlines filteredBSym + procs <- liftIO $ getNumProcessors withSolvers CVC5 (unsafeInto procs) (Just 100) $ \s -> do res <- equivalenceCheck s aPrgm bPrgm opts (mkCalldata Nothing []) - end <- getCurrentTime + end <- liftIO $ getCurrentTime case any isCex res of - False -> do + False -> liftIO $ do print $ "OK. Took " <> (show $ diffUTCTime end start) <> " seconds" let timeouts = filter isTimeout res unless (null timeouts) $ do - putStrLn $ "But " <> (show $ length timeouts) <> " timeout(s) occurred" + putStrLnM $ "But " <> (show $ length timeouts) <> " timeout(s) occurred" internalError "Encountered timeouts" - True -> do - putStrLn $ "Not OK: " <> show f <> " Got: " <> show res + True -> liftIO $ do + putStrLnM $ "Not OK: " <> show f <> " Got: " <> show res internalError "Was NOT equivalent" ) ] @@ -3116,22 +3144,27 @@ tests = testGroup "hevm" where (===>) = assertSolidityComputation -checkEquivProp :: Prop -> Prop -> IO Bool +checkEquivProp + :: (MonadUnliftIO m, ReadConfig m) + => Prop -> Prop -> m Bool checkEquivProp = checkEquivBase (\l r -> PNeg (PImpl l r .&& PImpl r l)) -checkEquiv :: (Typeable a) => Expr a -> Expr a -> IO Bool +checkEquiv + :: (Typeable a, MonadUnliftIO m, ReadConfig m) + => Expr a -> Expr a -> m Bool checkEquiv = checkEquivBase (./=) -checkEquivBase :: Eq a => (a -> a -> Prop) -> a -> a -> IO Bool -checkEquivBase mkprop l r = withSolvers Z3 1 (Just 1) $ \solvers -> do +checkEquivBase + :: (Eq a, MonadUnliftIO m, ReadConfig m) + => (a -> a -> Prop) -> a -> a -> m Bool +checkEquivBase mkprop l r = withSolvers Z3 1 (Just 1) $ \solvers -> liftIO $ do if l == r then do - putStrLn "skip" + putStrLnM "skip" pure True else do let smt = assertPropsNoSimp abstRefineDefault [mkprop l r] - let smt = assertProps abstRefineDefault [mkprop l r] - res <- checkSat solvers smt Nothing + res <- checkSat solvers smt print res pure $ case res of Unsat -> True @@ -3142,7 +3175,9 @@ checkEquivBase mkprop l r = withSolvers Z3 1 (Just 1) $ \solvers -> do -- | Takes a runtime code and calls it with the provided calldata -- | Takes a creation code and some calldata, runs the creation code, and calls the resulting contract with the provided calldata -runSimpleVM :: ByteString -> ByteString -> IO (Maybe ByteString) +runSimpleVM + :: (MonadUnliftIO m, ReadConfig m) + => ByteString -> ByteString -> m (Maybe ByteString) runSimpleVM x ins = do loadVM x >>= \case Nothing -> pure Nothing @@ -3155,9 +3190,11 @@ runSimpleVM x ins = do s -> internalError $ show s -- | Takes a creation code and returns a vm with the result of executing the creation code -loadVM :: ByteString -> IO (Maybe (VM RealWorld)) +loadVM + :: (MonadUnliftIO m, ReadConfig m) + => ByteString -> m (Maybe (VM RealWorld)) loadVM x = do - vm <- stToIO $ vmForEthrunCreation x + vm <- liftIO $ stToIO $ vmForEthrunCreation x vm1 <- Stepper.interpret (Fetch.zero 0 Nothing) vm Stepper.runFully case vm1.result of Just (VMSuccess (ConcreteBuf targetCode)) -> do @@ -3196,14 +3233,17 @@ defaultDataLocation t = then "memory" else "" -runFunction :: Text -> ByteString -> IO (Maybe ByteString) +runFunction + :: (MonadUnliftIO m, ReadConfig m) + => Text -> ByteString -> m (Maybe ByteString) runFunction c input = do - Just x <- singleContract "X" c - runSimpleVM x input + x <- liftIO $ singleContract "X" c + runSimpleVM (fromJust x) input runStatements - :: Text -> [AbiValue] -> AbiType - -> IO (Maybe ByteString) + :: (MonadUnliftIO m, ReadConfig m) + => Text -> [AbiValue] -> AbiType + -> m (Maybe ByteString) runStatements stmts args t = do let params = T.intercalate ", " @@ -3778,10 +3818,12 @@ data Invocation = SolidityCall Text [AbiValue] deriving Show -assertSolidityComputation :: Invocation -> AbiValue -> IO () +assertSolidityComputation + :: (MonadUnliftIO m, ReadConfig m) + => Invocation -> AbiValue -> m () assertSolidityComputation (SolidityCall s args) x = do y <- runStatements s args (abiValueType x) - assertEqual (T.unpack s) + liftIO $ assertEqual (T.unpack s) (fmap Bytes (Just (encodeAbiValue x))) (fmap Bytes y) @@ -3799,17 +3841,23 @@ checkBadCheatCode sig _ = \case (Failure _ _ (BadCheatCode s)) -> (ConcreteBuf $ into s.unFunctionSelector) ./= (ConcreteBuf $ selector sig) _ -> PBool True -allBranchesFail :: ByteString -> Maybe Sig -> IO (Either [SMTCex] (Expr End)) +allBranchesFail + :: (MonadUnliftIO m, ReadConfig m) + => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) allBranchesFail = checkPost (Just p) where p _ = \case Success _ _ _ _ -> PBool False _ -> PBool True -reachableUserAsserts :: ByteString -> Maybe Sig -> IO (Either [SMTCex] (Expr End)) +reachableUserAsserts + :: (MonadUnliftIO m, ReadConfig m) + => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) reachableUserAsserts = checkPost (Just $ checkAssertions [0x01]) -checkPost :: Maybe (Postcondition RealWorld) -> ByteString -> Maybe Sig -> IO (Either [SMTCex] (Expr End)) +checkPost + :: (MonadUnliftIO m, ReadConfig m) + => Maybe (Postcondition RealWorld) -> ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) checkPost post c sig = do (e, res) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c sig [] defaultVeriOpts Nothing post From 1b9c89c286947003bd8cf630bdb983850060aeff Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 9 Oct 2023 14:11:32 +0200 Subject: [PATCH 03/28] Adding changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a940cbc..db1808218 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ## API Changes From 1662425defdc6daf8f46849614e108bb25a28c69 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 9 Oct 2023 14:25:10 +0200 Subject: [PATCH 04/28] Fixing rebasing --- src/EVM/Solidity.hs | 6 ++++-- src/EVM/SymExec.hs | 2 +- test/test.hs | 18 +++++++++--------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/EVM/Solidity.hs b/src/EVM/Solidity.hs index f07686ba9..f16d93cc8 100644 --- a/src/EVM/Solidity.hs +++ b/src/EVM/Solidity.hs @@ -373,8 +373,10 @@ yulRuntime contractName src = do bytecode = c ^?! key "evm" ^?! key "deployedBytecode" ^?! key "object" % _String pure $ (toCode contractName) <$> (Just bytecode) -solidity :: Text -> Text -> IO (Maybe ByteString) -solidity contract src = do +solidity + :: (MonadUnliftIO m) + => Text -> Text -> m (Maybe ByteString) +solidity contract src = liftIO $ do (json, path) <- solidity' src let (Contracts sol, _, _) = fromJust $ readStdJSON json pure $ Map.lookup (path <> ":" <> contract) sol <&> (.creationCode) diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs index e61895b48..6f2b41a76 100644 --- a/src/EVM/SymExec.hs +++ b/src/EVM/SymExec.hs @@ -33,7 +33,7 @@ import EVM.Fetch qualified as Fetch import EVM.ABI import EVM.Expr qualified as Expr import EVM.Format (formatExpr, formatPartial, showVal, bsToHex) -import EVM.SMT (SMTCex(..), SMT2(..), assertProps, formatSMT2) +import EVM.SMT (SMTCex(..), SMT2(..), assertProps) import EVM.SMT qualified as SMT import EVM.Solvers import EVM.Stepper (Stepper) diff --git a/test/test.hs b/test/test.hs index 4a6e03817..0d9e73a70 100644 --- a/test/test.hs +++ b/test/test.hs @@ -939,7 +939,7 @@ tests = testGroup "hevm" ] , testGroup "Symbolic-Constructor-Args" -- this produced some hard to debug failures. keeping it around since it seemed to exercise the contract creation code in interesting ways... - [ testCase "multiple-symbolic-constructor-calls" $ do + [ test "multiple-symbolic-constructor-calls" $ do Just initCode <- solidity "C" [i| contract A { @@ -961,10 +961,10 @@ tests = testGroup "hevm" |] withSolvers Z3 1 Nothing $ \s -> do let calldata = (WriteWord (Lit 0x0) (Var "u") (ConcreteBuf ""), []) - initVM <- stToIO $ abstractVM calldata initCode Nothing True + initVM <- liftIO $ stToIO $ abstractVM calldata initCode Nothing True expr <- Expr.simplify <$> interpret (Fetch.oracle s Nothing) Nothing 1 StackBased initVM runExpr - assertBool "unexptected partial execution" (not $ Expr.containsNode isPartial expr) - , testCase "mixed-concrete-symbolic-args" $ do + assertBoolM "unexptected partial execution" (not $ Expr.containsNode isPartial expr) + , test "mixed-concrete-symbolic-args" $ do Just c <- solcRuntime "C" [i| contract B { @@ -986,8 +986,8 @@ tests = testGroup "hevm" } |] Right expr <- reachableUserAsserts c (Just $ Sig "foo(uint256)" [AbiUIntType 256]) - assertBool "unexptected partial execution" (not $ Expr.containsNode isPartial expr) - , testCase "jump-into-symbolic-region" $ do + assertBoolM "unexptected partial execution" (not $ Expr.containsNode isPartial expr) + , test "jump-into-symbolic-region" $ do let -- our initCode just jumps directly to the end code = BS.pack . mapMaybe maybeLitByte $ V.toList $ assemble @@ -1015,11 +1015,11 @@ tests = testGroup "hevm" , OpCreate ]) withSolvers Z3 1 Nothing $ \s -> do - vm <- stToIO $ loadSymVM runtimecode (Lit 0) initCode False + vm <- liftIO $ stToIO $ loadSymVM runtimecode (Lit 0) initCode False expr <- Expr.simplify <$> interpret (Fetch.oracle s Nothing) Nothing 1 StackBased vm runExpr case expr of - Partial _ _ (JumpIntoSymbolicCode _ _) -> assertBool "" True - _ -> assertBool "did not encounter expected partial node" False + Partial _ _ (JumpIntoSymbolicCode _ _) -> assertBoolM "" True + _ -> assertBoolM "did not encounter expected partial node" False ] , testGroup "Dapp-Tests" [ test "Trivial-Pass" $ do From a3e0e6d8c65609bbb1415ff70c58c6e7b3e2c88b Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 9 Oct 2023 15:01:16 +0200 Subject: [PATCH 05/28] Fixing up bench --- bench/bench.hs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bench/bench.hs b/bench/bench.hs index 7b66d1eff..2bcd34865 100644 --- a/bench/bench.hs +++ b/bench/bench.hs @@ -19,12 +19,17 @@ 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 import EVM.Test.BlockchainTests qualified as BCTests + +benchEnv :: Env +benchEnv = Env { config = Config { dumpQueries = True } } + main :: IO () main = defaultMain [ mkbench erc20 "erc20" 0 [1] @@ -76,7 +81,7 @@ runBCTest :: BCTests.Case -> IO Bool runBCTest x = do vm0 <- BCTests.vmForCase x - result <- Stepper.interpret (Fetch.zero 0 Nothing) vm0 Stepper.runFully + result <- runEnv benchEnv $ Stepper.interpret (Fetch.zero 0 Nothing) vm0 Stepper.runFully maybeReason <- BCTests.checkExpectation False x result pure $ isNothing maybeReason @@ -86,7 +91,7 @@ runBCTest x = findPanics :: Solver -> Natural -> Integer -> ByteString -> IO () findPanics solver count iters c = do - _ <- withSolvers solver count Nothing $ \s -> do + _ <- runEnv benchEnv $ withSolvers solver count Nothing $ \s -> do let opts = defaultVeriOpts { maxIter = Just iters , askSmtIters = iters + 1 From 2b28a25961b53a20f85e5e2bac4999a0da7139e2 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 9 Oct 2023 16:00:38 +0200 Subject: [PATCH 06/28] Fixing up rpc --- bench/bench.hs | 3 +-- test/rpc.hs | 32 ++++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/bench/bench.hs b/bench/bench.hs index 2bcd34865..e1437a40c 100644 --- a/bench/bench.hs +++ b/bench/bench.hs @@ -26,9 +26,8 @@ import qualified EVM.Fetch as Fetch import EVM.Test.BlockchainTests qualified as BCTests - benchEnv :: Env -benchEnv = Env { config = Config { dumpQueries = True } } +benchEnv = Env { config = Config { dumpQueries = False } } main :: IO () main = defaultMain diff --git a/test/rpc.hs b/test/rpc.hs index 1aefb453a..03f7d7af8 100644 --- a/test/rpc.hs +++ b/test/rpc.hs @@ -20,8 +20,17 @@ import EVM.Stepper qualified as Stepper import EVM.SymExec import EVM.Test.Utils import EVM.Solidity (ProjectType(..)) -import EVM.Types hiding (BlockNumber) +import EVM.Types hiding (BlockNumber, Env) import Control.Monad.ST (stToIO, RealWorld) +import Control.Monad.Reader (ReaderT) +import Control.Monad.IO.Unlift +import EVM.Effects + +rpcEnv :: Env +rpcEnv = Env { config = Config { dumpQueries = False } } + +test :: TestName -> ReaderT Env IO () -> TestTree +test a b = testCase a $ runEnv rpcEnv b main :: IO () main = defaultMain tests @@ -60,18 +69,20 @@ tests = testGroup "rpc" ] , testGroup "execution with remote state" -- execute against remote state from a ds-test harness - [ testCase "dapp-test" $ do + [ test "dapp-test" $ do let testFile = "test/contracts/pass/rpc.sol" - runSolidityTestCustom testFile ".*" Nothing Nothing False testRpcInfo Foundry >>= assertEqual "test result" True + -- runSolidityTestCustom testFile ".*" Nothing Nothing False testRpcInfo Foundry >>= assertEqual "test result" True + res <- runSolidityTestCustom testFile ".*" Nothing Nothing False testRpcInfo Foundry + liftIO $ assertEqual "test result" True res -- concretely exec "transfer" on WETH9 using remote rpc -- https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code - , testCase "weth-conc" $ do + , test "weth-conc" $ do let blockNum = 16198552 wad = 0x999999999999999999 calldata' = ConcreteBuf $ abiMethod "transfer(address,uint256)" (AbiTuple (V.fromList [AbiAddress (Addr 0xdead), AbiUInt 256 wad])) - vm <- weth9VM blockNum (calldata', []) + vm <- liftIO $ weth9VM blockNum (calldata', []) postVm <- withSolvers Z3 1 Nothing $ \solvers -> Stepper.interpret (oracle solvers (Just (BlockNumber blockNum, testRpc))) vm Stepper.runFully let @@ -83,21 +94,22 @@ tests = testGroup "rpc" msg = case postVm.result of Just (VMSuccess m) -> m _ -> internalError "VMSuccess expected" - assertEqual "should succeed" msg (ConcreteBuf $ word256Bytes 0x1) - assertEqual "should revert" receiverBal (W256 $ 2595433725034301 + wad) + liftIO $ do + assertEqual "should succeed" msg (ConcreteBuf $ word256Bytes 0x1) + assertEqual "should revert" receiverBal (W256 $ 2595433725034301 + wad) -- symbolically exec "transfer" on WETH9 using remote rpc -- https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code - , testCase "weth-sym" $ do + , test "weth-sym" $ do let blockNum = 16198552 calldata' = symCalldata "transfer(address,uint256)" [AbiAddressType, AbiUIntType 256] ["0xdead"] (AbstractBuf "txdata") postc _ (Failure _ _ (Revert _)) = PBool False postc _ _ = PBool True - vm <- weth9VM blockNum calldata' + vm <- liftIO $ weth9VM blockNum calldata' (_, [Cex (_, model)]) <- withSolvers Z3 1 Nothing $ \solvers -> verify solvers (rpcVeriOpts (BlockNumber blockNum, testRpc)) vm (Just postc) - assertBool "model should exceed caller balance" (getVar model "arg2" >= 695836005599316055372648) + liftIO $ assertBool "model should exceed caller balance" (getVar model "arg2" >= 695836005599316055372648) ] ] From f39458949f61f445426df1eac8faaa4f79983d1e Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 9 Oct 2023 16:44:17 +0200 Subject: [PATCH 07/28] Don't dump queries during BlockchainTests --- test/BlockchainTests.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/BlockchainTests.hs b/test/BlockchainTests.hs index 365519651..d0c2e9ecc 100644 --- a/test/BlockchainTests.hs +++ b/test/BlockchainTests.hs @@ -5,7 +5,7 @@ import Test.Tasty import EVM.Effects testEnv :: Env -testEnv = Env { config = Config { dumpQueries = True } } +testEnv = Env { config = Config { dumpQueries = False } } main :: IO () main = do From 9b20526c01e2f3299f57487817d2d845a7111612 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Tue, 10 Oct 2023 18:56:17 +0200 Subject: [PATCH 08/28] Cleaner setup --- .gitignore | 5 +++++ cli/cli.hs | 14 ++++++-------- src/EVM/Effects.hs | 14 ++++++++++++-- src/EVM/SymExec.hs | 19 ++++++++----------- src/EVM/UnitTest.hs | 4 +--- test/BlockchainTests.hs | 2 +- test/EVM/Test/BlockchainTests.hs | 2 +- test/EVM/Test/Tracing.hs | 2 +- test/EVM/Test/Utils.hs | 1 - test/rpc.hs | 2 +- test/test.hs | 27 ++++++++++++++++----------- 11 files changed, 52 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index f78b32318..c7e1881ed 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ dist/ dist-newstyle/ .configured cabal.project.local* + +# debug files +*.smt2 +*.prop +*.expr diff --git a/cli/cli.hs b/cli/cli.hs index 2f40e286a..ab74671dc 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -186,24 +186,25 @@ getFullVersion = showVersion Paths.version <> " [" <> gitVersion <> "]" Left _ -> "no git revision present" mainEnv :: Env -mainEnv = Env { config = Config { dumpQueries = True } } +mainEnv = Env { config = defaultConfig } main :: IO () main = do cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator" + let env = Env { config = defaultConfig { dumpQueries = cmd.smtdebug} } case cmd of Version {} ->putStrLn getFullVersion Symbolic {} -> do root <- getRoot cmd - withCurrentDirectory root $ runEnv mainEnv $ assert cmd - Equivalence {} -> runEnv mainEnv $ equivalence cmd - Exec {} -> runEnv mainEnv $ 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 - runEnv mainEnv $ withSolvers solver cores cmd.smttimeout $ \solvers -> do + runEnv env $ withSolvers solver cores cmd.smttimeout $ \solvers -> do buildOut <- liftIO $ readBuildOutput root (getProjectType cmd) case buildOut of Left e -> liftIO $ do @@ -222,7 +223,6 @@ 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 @@ -309,7 +309,6 @@ assert cmd = do 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 @@ -596,7 +595,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 diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs index 9f37649c0..5d9aac774 100644 --- a/src/EVM/Effects.hs +++ b/src/EVM/Effects.hs @@ -16,8 +16,18 @@ import Control.Monad.Reader -- This is a concrete datatype that contains handlers for the above effects inside the IO monad. data Config = Config - { - dumpQueries :: Bool + { dumpQueries :: Bool + , dumpExprs :: Bool + , dumpEndStates :: Bool + , verbose :: Bool + } + +defaultConfig :: Config +defaultConfig = Config { + dumpQueries = False + , dumpExprs = False + , dumpEndStates = False + , verbose = False } diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs index 6f2b41a76..5bf92e8c1 100644 --- a/src/EVM/SymExec.hs +++ b/src/EVM/SymExec.hs @@ -74,7 +74,6 @@ isQed _ = False data VeriOpts = VeriOpts { simp :: Bool - , debug :: Bool , maxIter :: Maybe Integer , askSmtIters :: Integer , loopHeuristic :: LoopHeuristic @@ -86,7 +85,6 @@ data VeriOpts = VeriOpts defaultVeriOpts :: VeriOpts defaultVeriOpts = VeriOpts { simp = True - , debug = False , maxIter = Nothing , askSmtIters = 1 , loopHeuristic = StackBased @@ -97,9 +95,6 @@ defaultVeriOpts = VeriOpts rpcVeriOpts :: (Fetch.BlockNumber, Text) -> VeriOpts rpcVeriOpts info = defaultVeriOpts { rpcInfo = Just info } -debugVeriOpts :: VeriOpts -debugVeriOpts = defaultVeriOpts { debug = True } - debugAbstVeriOpts :: VeriOpts debugAbstVeriOpts = defaultVeriOpts { abstRefineConfig = AbstRefineConfig True True } @@ -556,11 +551,12 @@ verify solvers opts preState maybepost = do liftIO $ putStrLn "Exploring contract" exprInter <- interpret (Fetch.oracle solvers opts.rpcInfo) opts.maxIter opts.askSmtIters opts.loopHeuristic preState runExpr - when opts.debug $ liftIO $ T.writeFile "unsimplified.expr" (formatExpr exprInter) + conf <- readConfig + when conf.dumpExprs $ liftIO $ T.writeFile "unsimplified.expr" (formatExpr exprInter) liftIO $ do putStrLn "Simplifying expression" let expr = if opts.simp then (Expr.simplify exprInter) else exprInter - when opts.debug $ liftIO $ T.writeFile "simplified.expr" (formatExpr expr) + when conf.dumpExprs $ T.writeFile "simplified.expr" (formatExpr expr) putStrLn $ "Explored contract (" <> show (Expr.numBranches expr) <> " branches)" @@ -588,7 +584,7 @@ verify solvers opts preState maybepost = do <> " potential property violation(s)" -- Dispatch the remaining branches to the solver to check for violations - results <- flip mapConcurrently withQueries $ \(query, leaf) -> do + results <- liftIO $ flip mapConcurrently withQueries $ \(query, leaf) -> do res <- checkSat solvers query pure (res, leaf) let cexs = filter (\(res, _) -> not . isUnsat $ res) results @@ -662,14 +658,15 @@ equivalenceCheck' solvers branchesA branchesB opts = do let allPairs = [(a,b) | a <- branchesA, b <- branchesB] liftIO $ putStrLn $ "Found " <> show (length allPairs) <> " total pairs of endstates" - when opts.debug $ liftIO $ + conf <- readConfig + when conf.dumpEndStates $ liftIO $ putStrLn $ "endstates in bytecodeA: " <> show (length branchesA) <> "\nendstates in bytecodeB: " <> show (length branchesB) let differingEndStates = sortBySize (mapMaybe (uncurry distinct) allPairs) liftIO $ putStrLn $ "Asking the SMT solver for " <> (show $ length differingEndStates) <> " pairs" - when opts.debug $ forM_ (zip differingEndStates [(1::Integer)..]) (\(x, i) -> - liftIO $ T.writeFile ("prop-checked-" <> show i) (T.pack $ show x)) + when conf.dumpEndStates $ forM_ (zip differingEndStates [(1::Integer)..]) (\(x, i) -> + liftIO $ T.writeFile ("prop-checked-" <> show i <> ".prop") (T.pack $ show x)) knownUnsat <- liftIO $ newTVarIO [] procs <- liftIO $ getNumProcessors diff --git a/src/EVM/UnitTest.hs b/src/EVM/UnitTest.hs index 036d4c6b1..dbf2781bd 100644 --- a/src/EVM/UnitTest.hs +++ b/src/EVM/UnitTest.hs @@ -52,7 +52,6 @@ data UnitTestOptions s = UnitTestOptions , verbose :: Maybe Int , maxIter :: Maybe Integer , askSmtIters :: Integer - , smtDebug :: Bool , smtTimeout :: Maybe Natural , solver :: Maybe Text , match :: Text @@ -98,8 +97,7 @@ type ABIMethod = Text -- | Generate VeriOpts from UnitTestOptions makeVeriOpts :: UnitTestOptions s -> VeriOpts makeVeriOpts opts = - defaultVeriOpts { debug = opts.smtDebug - , maxIter = opts.maxIter + defaultVeriOpts { maxIter = opts.maxIter , askSmtIters = opts.askSmtIters , rpcInfo = opts.rpcInfo } diff --git a/test/BlockchainTests.hs b/test/BlockchainTests.hs index d0c2e9ecc..5606ec2f9 100644 --- a/test/BlockchainTests.hs +++ b/test/BlockchainTests.hs @@ -5,7 +5,7 @@ import Test.Tasty import EVM.Effects testEnv :: Env -testEnv = Env { config = Config { dumpQueries = False } } +testEnv = Env { config = defaultConfig } main :: IO () main = do diff --git a/test/EVM/Test/BlockchainTests.hs b/test/EVM/Test/BlockchainTests.hs index d598d7cd9..5e720fdcb 100644 --- a/test/EVM/Test/BlockchainTests.hs +++ b/test/EVM/Test/BlockchainTests.hs @@ -66,7 +66,7 @@ data BlockchainCase = BlockchainCase testEnv :: Env -testEnv = Env { config = Config { dumpQueries = True } } +testEnv = Env { config = defaultConfig } main :: IO () main = do diff --git a/test/EVM/Test/Tracing.hs b/test/EVM/Test/Tracing.hs index 8041e6b2b..f6a698710 100644 --- a/test/EVM/Test/Tracing.hs +++ b/test/EVM/Test/Tracing.hs @@ -833,7 +833,7 @@ getOp vm = else BS.head xs testEnv :: Env -testEnv = Env { config = Config { dumpQueries = True } } +testEnv = Env { config = defaultConfig } test :: TestName -> ReaderT Env IO () -> TestTree test a b = testCase a $ runEnv testEnv b diff --git a/test/EVM/Test/Utils.hs b/test/EVM/Test/Utils.hs index f11e7d7da..c62dc1f15 100644 --- a/test/EVM/Test/Utils.hs +++ b/test/EVM/Test/Utils.hs @@ -52,7 +52,6 @@ testOpts solvers root buildOutput match maxIter allowFFI rpcinfo = do , rpcInfo = rpcinfo , maxIter = maxIter , askSmtIters = 1 - , smtDebug = False , smtTimeout = Nothing , solver = Nothing , verbose = Just 1 diff --git a/test/rpc.hs b/test/rpc.hs index 03f7d7af8..c4b9644cd 100644 --- a/test/rpc.hs +++ b/test/rpc.hs @@ -27,7 +27,7 @@ import Control.Monad.IO.Unlift import EVM.Effects rpcEnv :: Env -rpcEnv = Env { config = Config { dumpQueries = False } } +rpcEnv = Env { config = defaultConfig } test :: TestName -> ReaderT Env IO () -> TestTree test a b = testCase a $ runEnv rpcEnv b diff --git a/test/test.hs b/test/test.hs index 0d9e73a70..3525b0e59 100644 --- a/test/test.hs +++ b/test/test.hs @@ -70,7 +70,11 @@ import EVM.Types hiding (Env) import EVM.Effects testEnv :: Env -testEnv = Env { config = Config { dumpQueries = False } } +testEnv = Env { config = defaultConfig { + dumpQueries = True + , dumpExprs = True + , dumpEndStates = True + } } putStrLnM :: (MonadUnliftIO m) => String -> m () putStrLnM a = liftIO $ putStrLn a @@ -117,7 +121,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts assertEqualM "Expression is not clean." (badStoresInExpr expr) False -- This case is somewhat artificial. We can't simplify this using only -- static rewrite rules, because acct is totally abstract and acct + 1 @@ -137,7 +141,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts -- T.writeFile "symbolic-index.expr" $ formatExpr expr assertEqualM "Expression is not clean." (badStoresInExpr expr) False , expectFail $ test "simplify-storage-array-of-struct-symbolic-index" $ do @@ -158,7 +162,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts assertEqualM "Expression is not clean." (badStoresInExpr expr) False , test "simplify-storage-array-loop-nonstruct" $ do Just c <- solcRuntime "MyContract" @@ -173,7 +177,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256)" [AbiUIntType 256])) [] (debugVeriOpts { maxIter = Just 5 }) + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256)" [AbiUIntType 256])) [] (defaultVeriOpts { maxIter = Just 5 }) assertEqualM "Expression is not clean." (badStoresInExpr expr) False , test "simplify-storage-array-loop-struct" $ do Just c <- solcRuntime "MyContract" @@ -193,7 +197,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] (debugVeriOpts { maxIter = Just 5 }) + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256)" [AbiUIntType 256, AbiUIntType 256])) [] (defaultVeriOpts { maxIter = Just 5 }) assertEqualM "Expression is not clean." (badStoresInExpr expr) False , test "simplify-storage-map-only-static" $ do Just c <- solcRuntime "MyContract" @@ -209,7 +213,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts assertEqualM "Expression is not clean." (badStoresInExpr expr) False , test "simplify-storage-map-only-2" $ do Just c <- solcRuntime "MyContract" @@ -225,7 +229,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts -- putStrLnM $ T.unpack $ formatExpr expr assertEqualM "Expression is not clean." (badStoresInExpr expr) False , test "simplify-storage-map-with-struct" $ do @@ -246,7 +250,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts assertEqualM "Expression is not clean." (badStoresInExpr expr) False , test "simplify-storage-map-and-array" $ do Just c <- solcRuntime "MyContract" @@ -268,7 +272,7 @@ tests = testGroup "hevm" } } |] - expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] debugVeriOpts + expr <- withSolvers Z3 1 Nothing $ \s -> getExpr s c (Just (Sig "transfer(uint256,uint256,uint256)" [AbiUIntType 256, AbiUIntType 256, AbiUIntType 256])) [] defaultVeriOpts -- putStrLnM $ T.unpack $ formatExpr expr assertEqualM "Expression is not clean." (badStoresInExpr expr) False ] @@ -3114,7 +3118,8 @@ tests = testGroup "hevm" filteredBSym = symbolicMem [ replaceAll "" $ x *=~[re|^//|] | x <- onlyAfter [re|^// step:|] unfiltered, not $ x =~ [re|^$|] ] start <- liftIO $ getCurrentTime putStrLnM $ "Checking file: " <> f - when opts.debug $ liftIO $ do + conf <- readConfig + when conf.verbose $ liftIO $ do putStrLnM "-------------Original Below-----------------" mapM_ putStrLn unfiltered putStrLnM "------------- Filtered A + Symb below-----------------" From 43f79d5a7e99f862c2404cf71bfd30a8c69c47a5 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Tue, 10 Oct 2023 19:00:41 +0200 Subject: [PATCH 09/28] Not so much debug --- test/test.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test.hs b/test/test.hs index 3525b0e59..9f1282235 100644 --- a/test/test.hs +++ b/test/test.hs @@ -71,9 +71,9 @@ import EVM.Effects testEnv :: Env testEnv = Env { config = defaultConfig { - dumpQueries = True - , dumpExprs = True - , dumpEndStates = True + dumpQueries = False + , dumpExprs = False + , dumpEndStates = False } } putStrLnM :: (MonadUnliftIO m) => String -> m () From 00649365e1edd194be573c972c7c7b5ba68b8626 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Wed, 11 Oct 2023 10:57:18 +0200 Subject: [PATCH 10/28] Fixing bench --- bench/bench.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/bench.hs b/bench/bench.hs index e1437a40c..d82ad2114 100644 --- a/bench/bench.hs +++ b/bench/bench.hs @@ -27,7 +27,7 @@ import qualified EVM.Fetch as Fetch import EVM.Test.BlockchainTests qualified as BCTests benchEnv :: Env -benchEnv = Env { config = Config { dumpQueries = False } } +benchEnv = Env { config = defaultConfig } main :: IO () main = defaultMain From 3b05c8509fa0a1dd36530cc0060bdb346d27644f Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Wed, 11 Oct 2023 12:56:54 +0200 Subject: [PATCH 11/28] Also add abstraction-refinement --- cli/cli.hs | 35 +++++++++++++++------------- src/EVM/Effects.hs | 5 ++++ src/EVM/Fetch.hs | 9 +++---- src/EVM/SMT.hs | 33 ++++++++++++++------------ src/EVM/SymExec.hs | 58 +++++++++++++++++++++++++--------------------- src/EVM/Types.hs | 9 ------- test/test.hs | 30 +++++++++++++----------- 7 files changed, 94 insertions(+), 85 deletions(-) diff --git a/cli/cli.hs b/cli/cli.hs index ab74671dc..3bd38ebef 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -191,7 +191,10 @@ mainEnv = Env { config = defaultConfig } main :: IO () main = do cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator" - let env = Env { config = defaultConfig { dumpQueries = cmd.smtdebug} } + let env = Env { config = defaultConfig { + dumpQueries = cmd.smtdebug + , abstRefineMem = cmd.abstractMemory + , abstRefineArith = cmd.abstractArithmetic} } case cmd of Version {} ->putStrLn getFullVersion Symbolic {} -> do @@ -226,7 +229,6 @@ equivalence cmd = do , maxIter = cmd.maxIterations , askSmtIters = cmd.askSmtIterations , loopHeuristic = cmd.loopDetectionHeuristic - , abstRefineConfig = AbstRefineConfig cmd.abstractArithmetic cmd.abstractMemory , rpcInfo = Nothing } calldata <- liftIO $ buildCalldata cmd @@ -312,13 +314,12 @@ assert cmd = do , 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 _] -> liftIO $ do - putStrLn "\nQED: No reachable property violations discovered\n" + [Qed _] -> do + liftIO $ putStrLn "\nQED: No reachable property violations discovered\n" showExtras solvers cmd calldata expr _ -> do let cexs = snd <$> mapMaybe getCex res @@ -337,26 +338,28 @@ assert cmd = do , "Could not determine reachability of the following end states:" , "" ] <> fmap (formatExpr) timeouts - liftIO $ do - T.putStrLn $ T.unlines (counterexamples <> unknowns) - showExtras solvers cmd calldata expr - exitFailure + liftIO $ T.putStrLn $ T.unlines (counterexamples <> unknowns) + showExtras solvers cmd calldata expr + liftIO $ exitFailure -showExtras :: SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> IO () +showExtras + :: (MonadUnliftIO m, ReadConfig 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 diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs index 5d9aac774..6d56a1b1f 100644 --- a/src/EVM/Effects.hs +++ b/src/EVM/Effects.hs @@ -20,7 +20,10 @@ data Config = Config , dumpExprs :: Bool , dumpEndStates :: Bool , verbose :: Bool + , abstRefineArith :: Bool + , abstRefineMem :: Bool } + deriving (Show, Eq) defaultConfig :: Config defaultConfig = Config { @@ -28,6 +31,8 @@ defaultConfig = Config { , dumpExprs = False , dumpEndStates = False , verbose = False + , abstRefineArith = False + , abstRefineMem = False } diff --git a/src/EVM/Fetch.hs b/src/EVM/Fetch.hs index 109e88990..c9763484f 100644 --- a/src/EVM/Fetch.hs +++ b/src/EVM/Fetch.hs @@ -240,14 +240,15 @@ type Fetcher m s = (ReadConfig m, MonadUnliftIO m) => Query s -> m (EVM s ()) -- but for normal execution paths with inconsistent pathconditions -- will be pruned anyway. checkBranch :: (MonadUnliftIO m, ReadConfig m) => SolverGroup -> Prop -> Prop -> m BranchCondition -checkBranch solvers branchcondition pathconditions = liftIO $ do - checkSat solvers (assertProps abstRefineDefault [(branchcondition .&& pathconditions)]) >>= \case +checkBranch solvers branchcondition pathconditions = do + conf <- readConfig + liftIO $ checkSat solvers (assertProps conf [(branchcondition .&& pathconditions)]) >>= \case -- the condition is unsatisfiable Unsat -> -- if pathconditions are consistent then the condition must be false pure $ Case False -- Sat means its possible for condition to hold - Sat _ -> -- is its negation also possible? - checkSat solvers (assertProps abstRefineDefault [(pathconditions .&& (PNeg branchcondition))]) >>= \case + Sat _ -> do -- is its negation also possible? + checkSat solvers (assertProps conf [(pathconditions .&& (PNeg branchcondition))]) >>= \case -- No. The condition must hold Unsat -> pure $ Case True -- Yes. Both branches possible diff --git a/src/EVM/SMT.hs b/src/EVM/SMT.hs index 2e5d46e7a..174769fd5 100644 --- a/src/EVM/SMT.hs +++ b/src/EVM/SMT.hs @@ -31,7 +31,7 @@ import Language.SMT2.Parser (getValueRes, parseCommentFreeFileMsg) import Language.SMT2.Syntax (Symbol, SpecConstant(..), GeneralRes(..), Term(..), QualIdentifier(..), Identifier(..), Sort(..), Index(..), VarBinding(..)) import Numeric (readHex, readBin) import Witch (into, unsafeInto) -import Control.Monad.State +import Control.Monad.State (State, runState, get, put) import EVM.Format (formatProp) import EVM.CSE @@ -40,6 +40,7 @@ import EVM.Expr qualified as Expr import EVM.Keccak (keccakAssumptions, keccakCompute) import EVM.Traversals import EVM.Types +import EVM.Effects -- ** Encoding ** ---------------------------------------------------------------------------------- @@ -171,21 +172,21 @@ data AbstState = AbstState } deriving (Show) -abstractAwayProps :: AbstRefineConfig -> [Prop] -> ([Prop], AbstState) -abstractAwayProps abstRefineConfig ps = runState (mapM abstrAway ps) (AbstState mempty 0) +abstractAwayProps :: Config -> [Prop] -> ([Prop], AbstState) +abstractAwayProps conf ps = runState (mapM abstrAway ps) (AbstState mempty 0) where abstrAway :: Prop -> State AbstState Prop abstrAway prop = mapPropM go prop go :: Expr a -> State AbstState (Expr a) go x = case x of - e@(Mod{}) | abstRefineConfig.arith -> abstrExpr e - e@(SMod{}) | abstRefineConfig.arith -> abstrExpr e - e@(MulMod{}) | abstRefineConfig.arith -> abstrExpr e - e@(AddMod{}) | abstRefineConfig.arith -> abstrExpr e - e@(Mul{}) | abstRefineConfig.arith -> abstrExpr e - e@(Div{}) | abstRefineConfig.arith -> abstrExpr e - e@(SDiv {}) | abstRefineConfig.arith -> abstrExpr e - e@(ReadWord {}) | abstRefineConfig.mem -> abstrExpr e + e@(Mod{}) | conf.abstRefineArith -> abstrExpr e + e@(SMod{}) | conf.abstRefineArith -> abstrExpr e + e@(MulMod{}) | conf.abstRefineArith -> abstrExpr e + e@(AddMod{}) | conf.abstRefineArith -> abstrExpr e + e@(Mul{}) | conf.abstRefineArith -> abstrExpr e + e@(Div{}) | conf.abstRefineArith -> abstrExpr e + e@(SDiv {}) | conf.abstRefineArith -> abstrExpr e + e@(ReadWord {}) | conf.abstRefineMem -> abstrExpr e e -> pure e where abstrExpr e = do @@ -203,13 +204,15 @@ smt2Line :: Builder -> SMT2 smt2Line txt = SMT2 [txt] mempty mempty mempty assertProps :: AbstRefineConfig -> [Prop] -> SMT2 +assertProps :: Config -> [Prop] -> SMT2 +-- first simplify to rewrite sload/sstore combos, then concretize&simplify assertProps conf ps = assertPropsNoSimp conf (Expr.simplifyProps ps) -- Note: we need a version that does NOT call simplify or simplifyProps, -- because we make use of it to verify the correctness of our simplification -- passes through property-based testing. -assertPropsNoSimp :: AbstRefineConfig -> [Prop] -> SMT2 -assertPropsNoSimp abstRefineConfig ps = +assertPropsNoSimp :: Config -> [Prop] -> SMT2 +assertPropsNoSimp config psPreConc = let encs = map propToSMT psElimAbst abstSMT = map propToSMT abstProps intermediates = declareIntermediates bufs stores in @@ -238,8 +241,8 @@ assertPropsNoSimp abstRefineConfig ps = where (psElim, bufs, stores) = eliminateProps ps - (psElimAbst, abst@(AbstState abstExprToInt _)) = if abstRefineConfig.arith || abstRefineConfig.mem - then abstractAwayProps abstRefineConfig psElim + (psElimAbst, abst@(AbstState abstExprToInt _)) = if config.abstRefineArith || config.abstRefineMem + then abstractAwayProps config psElim else (psElim, AbstState mempty 0) abstProps = map toProp (Map.toList abstExprToInt) diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs index 5bf92e8c1..882df4e8f 100644 --- a/src/EVM/SymExec.hs +++ b/src/EVM/SymExec.hs @@ -77,7 +77,6 @@ data VeriOpts = VeriOpts , maxIter :: Maybe Integer , askSmtIters :: Integer , loopHeuristic :: LoopHeuristic - , abstRefineConfig :: AbstRefineConfig , rpcInfo :: Fetch.RpcInfo } deriving (Eq, Show) @@ -88,16 +87,12 @@ defaultVeriOpts = VeriOpts , maxIter = Nothing , askSmtIters = 1 , loopHeuristic = StackBased - , abstRefineConfig = abstRefineDefault , rpcInfo = Nothing } rpcVeriOpts :: (Fetch.BlockNumber, Text) -> VeriOpts rpcVeriOpts info = defaultVeriOpts { rpcInfo = Just info } -debugAbstVeriOpts :: VeriOpts -debugAbstVeriOpts = defaultVeriOpts { abstRefineConfig = AbstRefineConfig True True } - extractCex :: VerifyResult -> Maybe (Expr End, SMTCex) extractCex (Cex c) = Just c extractCex _ = Nothing @@ -486,9 +481,12 @@ flattenExpr = go [] -- the incremental nature of the task at hand. Introducing support for -- incremental queries might let us go even faster here. -- TODO: handle errors properly -reachable :: SolverGroup -> Expr End -> IO ([SMT2], Expr End) +reachable + :: (MonadUnliftIO m, ReadConfig m) + => SolverGroup -> Expr End -> m ([SMT2], Expr End) reachable solvers e = do - res <- go [] e + conf <- readConfig + res <- liftIO $ go conf [] e pure $ second (fromMaybe (internalError "no reachable paths found")) res where {- @@ -497,12 +495,12 @@ reachable solvers e = do If reachable return the expr wrapped in a Just. If not return Nothing. When walking back up the tree drop unreachable subbranches. -} - go :: [Prop] -> Expr End -> IO ([SMT2], Maybe (Expr End)) - go pcs = \case + go :: Config -> [Prop] -> Expr End -> IO ([SMT2], Maybe (Expr End)) + go conf pcs = \case ITE c t f -> do (tres, fres) <- concurrently - (go (PEq (Lit 1) c : pcs) t) - (go (PEq (Lit 0) c : pcs) f) + (go conf (PEq (Lit 1) c : pcs) t) + (go conf (PEq (Lit 0) c : pcs) f) let subexpr = case (snd tres, snd fres) of (Just t', Just f') -> Just $ ITE c t' f' (Just t', Nothing) -> Just t' @@ -510,7 +508,7 @@ reachable solvers e = do (Nothing, Nothing) -> Nothing pure (fst tres <> fst fres, subexpr) leaf -> do - let query = assertProps abstRefineDefault pcs + let query = assertProps conf pcs res <- checkSat solvers query case res of Sat _ -> pure ([query], Just leaf) @@ -578,7 +576,7 @@ verify solvers opts preState maybepost = do _ -> True assumes = preState.constraints withQueries = canViolate <&> \leaf -> - (assertProps opts.abstRefineConfig (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf) + (assertProps conf (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf) putStrLn $ "Checking for reachability of " <> show (length withQueries) <> " potential property violation(s)" @@ -633,7 +631,7 @@ equivalenceCheck solvers bytecodeA bytecodeB opts calldata = do False -> do branchesA <- getBranches bytecodeA branchesB <- getBranches bytecodeB - equivalenceCheck' solvers branchesA branchesB opts + equivalenceCheck' solvers branchesA branchesB where -- decompiles the given bytecode into a list of branches getBranches :: ByteString -> m [Expr End] @@ -647,8 +645,8 @@ equivalenceCheck solvers bytecodeA bytecodeB opts calldata = do equivalenceCheck' :: forall m . (MonadUnliftIO m, ReadConfig m) - => SolverGroup -> [Expr End] -> [Expr End] -> VeriOpts -> m [EquivResult] -equivalenceCheck' solvers branchesA branchesB opts = do + => SolverGroup -> [Expr End] -> [Expr End] -> m [EquivResult] +equivalenceCheck' solvers branchesA branchesB = do when (any isPartial branchesA || any isPartial branchesB) $ liftIO $ do putStrLn "" putStrLn "WARNING: hevm was only able to partially explore the given contract due to the following issues:" @@ -669,8 +667,8 @@ equivalenceCheck' solvers branchesA branchesB opts = do liftIO $ T.writeFile ("prop-checked-" <> show i <> ".prop") (T.pack $ show x)) knownUnsat <- liftIO $ newTVarIO [] - procs <- liftIO $ getNumProcessors - results <- liftIO $ checkAll differingEndStates knownUnsat procs + procs <- liftIO getNumProcessors + results <- checkAll differingEndStates knownUnsat procs let useful = foldr (\(_, b) n -> if b then n+1 else n) (0::Integer) results liftIO $ putStrLn $ "Reuse of previous queries was Useful in " <> (show useful) <> " cases" @@ -691,9 +689,9 @@ equivalenceCheck' solvers branchesA branchesB opts = do -- the solver if we can determine unsatisfiability from the cache already -- the last element of the returned tuple indicates whether the cache was -- used or not - check :: UnsatCache -> (Set Prop) -> IO (EquivResult, Bool) - check knownUnsat props = do - let smt = assertProps opts.abstRefineConfig (Set.toList props) + check :: Config -> UnsatCache -> (Set Prop) -> IO (EquivResult, Bool) + check conf knownUnsat props = do + let smt = assertProps conf (Set.toList props) ku <- readTVarIO knownUnsat res <- if subsetAny props ku then pure (True, Unsat) @@ -715,10 +713,13 @@ equivalenceCheck' solvers branchesA branchesB opts = do -- from left-to-right, and with a max of K threads. This is in contrast to -- mapConcurrently which would spawn as many threads as there are jobs, and -- run them in a random order. We ordered them correctly, though so that'd be bad - checkAll :: [(Set Prop)] -> UnsatCache -> Int -> IO [(EquivResult, Bool)] + checkAll + :: (MonadUnliftIO m, ReadConfig m) + => [(Set Prop)] -> UnsatCache -> Int -> m [(EquivResult, Bool)] checkAll input cache numproc = do - wrap <- pool numproc - parMapIO (wrap . (check cache)) input + conf <- readConfig + wrap <- liftIO $ pool numproc + liftIO $ parMapIO (wrap . (check conf cache)) input -- Takes two branches and returns a set of props that will need to be @@ -785,11 +786,14 @@ equivalenceCheck' solvers branchesA branchesB opts = do both' :: (a -> b) -> (a, a) -> (b, b) both' f (x, y) = (f x, f y) -produceModels :: SolverGroup -> Expr End -> IO [(Expr End, CheckSatResult)] +produceModels + :: (MonadUnliftIO m, ReadConfig m) + => SolverGroup -> Expr End -> m [(Expr End, CheckSatResult)] produceModels solvers expr = do let flattened = flattenExpr expr - withQueries = fmap (\e -> ((assertProps abstRefineDefault) . extractProps $ e, e)) flattened - results <- flip mapConcurrently withQueries $ \(query, leaf) -> do + withQueries conf = fmap (\e -> ((assertProps conf) . extractProps $ e, e)) flattened + conf <- readConfig + results <- liftIO $ (flip mapConcurrently) (withQueries conf) $ \(query, leaf) -> do res <- checkSat solvers query pure (res, leaf) pure $ fmap swap $ filter (\(res, _) -> not . isUnsat $ res) results diff --git a/src/EVM/Types.hs b/src/EVM/Types.hs index b5d0d4902..13296066e 100644 --- a/src/EVM/Types.hs +++ b/src/EVM/Types.hs @@ -621,15 +621,6 @@ data RuntimeConfig = RuntimeConfig } deriving (Show) -abstRefineDefault :: AbstRefineConfig -abstRefineDefault = AbstRefineConfig False False - -data AbstRefineConfig = AbstRefineConfig - { arith :: Bool - , mem :: Bool - } - deriving (Show, Eq) - -- | An entry in the VM's "call/create stack" data Frame s = Frame { context :: FrameContext diff --git a/test/test.hs b/test/test.hs index 9f1282235..458fe9586 100644 --- a/test/test.hs +++ b/test/test.hs @@ -3162,20 +3162,22 @@ checkEquiv = checkEquivBase (./=) checkEquivBase :: (Eq a, MonadUnliftIO m, ReadConfig m) => (a -> a -> Prop) -> a -> a -> m Bool -checkEquivBase mkprop l r = withSolvers Z3 1 (Just 1) $ \solvers -> liftIO $ do - if l == r - then do - putStrLnM "skip" - pure True - else do - let smt = assertPropsNoSimp abstRefineDefault [mkprop l r] - res <- checkSat solvers smt - print res - pure $ case res of - Unsat -> True - EVM.Solvers.Unknown -> True - Sat _ -> False - Error _ -> False +checkEquivBase mkprop l r = do + conf <- readConfig + withSolvers Z3 1 (Just 1) $ \solvers -> liftIO $ do + if l == r + then do + putStrLnM "skip" + pure True + else do + let smt = assertPropsNoSimp conf [mkprop l r] + res <- checkSat solvers smt + print res + pure $ case res of + Unsat -> True + EVM.Solvers.Unknown -> True + Sat _ -> False + Error _ -> False -- | Takes a runtime code and calls it with the provided calldata From 03fea0ec749a6860d874ff00c5df93c3f171aa1c Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Wed, 11 Oct 2023 13:03:56 +0200 Subject: [PATCH 12/28] Fixing up some minor things --- src/EVM/SMT.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EVM/SMT.hs b/src/EVM/SMT.hs index 174769fd5..ea223a963 100644 --- a/src/EVM/SMT.hs +++ b/src/EVM/SMT.hs @@ -203,7 +203,6 @@ abstractAwayProps conf ps = runState (mapM abstrAway ps) (AbstState mempty 0) smt2Line :: Builder -> SMT2 smt2Line txt = SMT2 [txt] mempty mempty mempty -assertProps :: AbstRefineConfig -> [Prop] -> SMT2 assertProps :: Config -> [Prop] -> SMT2 -- first simplify to rewrite sload/sstore combos, then concretize&simplify assertProps conf ps = assertPropsNoSimp conf (Expr.simplifyProps ps) @@ -212,7 +211,7 @@ assertProps conf ps = assertPropsNoSimp conf (Expr.simplifyProps ps) -- because we make use of it to verify the correctness of our simplification -- passes through property-based testing. assertPropsNoSimp :: Config -> [Prop] -> SMT2 -assertPropsNoSimp config psPreConc = +assertPropsNoSimp config ps = let encs = map propToSMT psElimAbst abstSMT = map propToSMT abstProps intermediates = declareIntermediates bufs stores in From a6bb1321f6f3a83828d56bed7754772999929bdf Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Thu, 12 Oct 2023 16:10:29 +0200 Subject: [PATCH 13/28] Adding tracing to places --- bench/bench.hs | 25 ++++++++++++++++--------- cli/cli.hs | 5 +++-- hevm.cabal | 3 ++- src/EVM/Effects.hs | 31 +++++++++++++++++++++++++++---- src/EVM/UnitTest.hs | 2 ++ test/EVM/Test/BlockchainTests.hs | 12 +++++++----- test/test.hs | 1 + 7 files changed, 58 insertions(+), 21 deletions(-) diff --git a/bench/bench.hs b/bench/bench.hs index d82ad2114..30cd890b5 100644 --- a/bench/bench.hs +++ b/bench/bench.hs @@ -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) @@ -71,16 +72,20 @@ blockchainTests ts = bench "blockchain-tests" $ nfIO $ do if n `elem` ignored then pure True else do - res <- runBCTest c + res <- runEnv benchEnv $ 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 + :: (MonadUnliftIO m, ReadConfig m) + => BCTests.Case -> m Bool runBCTest x = do - vm0 <- BCTests.vmForCase x - result <- runEnv benchEnv $ Stepper.interpret (Fetch.zero 0 Nothing) vm0 Stepper.runFully + 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 @@ -88,15 +93,17 @@ runBCTest x = --- Helpers ---------------------------------------------------------------------------------------- -findPanics :: Solver -> Natural -> Integer -> ByteString -> IO () +findPanics + :: (MonadUnliftIO m, ReadConfig m) + => Solver -> Natural -> Integer -> ByteString -> m () findPanics solver count iters c = do - _ <- runEnv benchEnv $ withSolvers solver count Nothing $ \s -> 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 @@ -107,8 +114,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 $ runEnv benchEnv $ findPanics CVC5 i iters c' + , bench ("z3-" <> show i) $ nfIO $ runEnv benchEnv $ findPanics Z3 i iters c' ] | i <- counts ] diff --git a/cli/cli.hs b/cli/cli.hs index 3bd38ebef..b3bd26757 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -194,7 +194,8 @@ main = do let env = Env { config = defaultConfig { dumpQueries = cmd.smtdebug , abstRefineMem = cmd.abstractMemory - , abstRefineArith = cmd.abstractArithmetic} } + , abstRefineArith = cmd.abstractArithmetic + , dumpTrace = cmd.trace } } case cmd of Version {} ->putStrLn getFullVersion Symbolic {} -> do @@ -380,7 +381,7 @@ launchExec cmd = do -- 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 - liftIO $ when cmd.trace $ T.hPutStr stderr (showTraceTree dapp vm') + writeTraceDapp dapp vm' case vm'.result of Just (VMFailure (Revert msg)) -> liftIO $ do let res = case msg of diff --git a/hevm.cabal b/hevm.cabal index 07080988c..79b58f745 100644 --- a/hevm.cabal +++ b/hevm.cabal @@ -375,4 +375,5 @@ benchmark bench filemanip, filepath, containers, - mtl + mtl, + unliftio-core diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs index 6d56a1b1f..08672ac53 100644 --- a/src/EVM/Effects.hs +++ b/src/EVM/Effects.hs @@ -10,18 +10,26 @@ module EVM.Effects where import Control.Monad.Reader +import Control.Monad.IO.Unlift +import EVM.Dapp (DappInfo) +import EVM.Types (VM(..)) +import Control.Monad.ST (RealWorld) +import Data.Text.IO as T +import EVM.Format (showTraceTree) +import EVM (traceForest) -- Abstract Effects -------------------------------------------------------------------------------- -- Here we define the abstract interface for the effects that we wish to model -- This is a concrete datatype that contains handlers for the above effects inside the IO monad. data Config = Config - { dumpQueries :: Bool - , dumpExprs :: Bool - , dumpEndStates :: Bool - , verbose :: Bool + { dumpQueries :: Bool + , dumpExprs :: Bool + , dumpEndStates :: Bool + , verbose :: Bool , abstRefineArith :: Bool , abstRefineMem :: Bool + , dumpTrace :: Bool } deriving (Show, Eq) @@ -33,8 +41,23 @@ defaultConfig = Config { , verbose = False , abstRefineArith = False , abstRefineMem = False + , dumpTrace = False } +writeTraceDapp + :: (MonadUnliftIO m, ReadConfig m) + => DappInfo -> VM RealWorld -> m () +writeTraceDapp dapp vm = do + conf <- readConfig + liftIO $ when conf.dumpTrace $ T.writeFile "VM.trace" (showTraceTree dapp vm) + +writeTrace + :: (MonadUnliftIO m, ReadConfig m) + => VM RealWorld -> m () +writeTrace vm = do + conf <- readConfig + liftIO $ when conf.dumpTrace $ Prelude.writeFile "VM.trace" (show $ traceForest vm) + data Env = Env { config :: Config diff --git a/src/EVM/UnitTest.hs b/src/EVM/UnitTest.hs index dbf2781bd..73cc81a73 100644 --- a/src/EVM/UnitTest.hs +++ b/src/EVM/UnitTest.hs @@ -170,6 +170,7 @@ runUnitTestContract initializeUnitTest opts theContract Stepper.evm get + writeTraceDapp dapp vm1 case vm1.result of Just (VMFailure _) -> liftIO $ do Text.putStrLn "\x1b[31m[BAIL]\x1b[0m setUp() " @@ -232,6 +233,7 @@ symRun opts@UnitTestOptions{..} vm (Sig testName types) = do pushTrace (EntryTrace testName) makeTxCall testParams cd get + writeTraceDapp dapp vm' -- check postconditions against vm (e, results) <- verify solvers (makeVeriOpts opts) vm' (Just postcondition) diff --git a/test/EVM/Test/BlockchainTests.hs b/test/EVM/Test/BlockchainTests.hs index 5e720fdcb..74cfa17dc 100644 --- a/test/EVM/Test/BlockchainTests.hs +++ b/test/EVM/Test/BlockchainTests.hs @@ -144,9 +144,9 @@ runVMTest runVMTest diffmode (_name, x) = do vm0 <- liftIO $ vmForCase x result <- EVM.Stepper.interpret (EVM.Fetch.zero 0 (Just 0)) vm0 EVM.Stepper.runFully - liftIO $ do - maybeReason <- checkExpectation diffmode x result - forM_ maybeReason assertFailure + writeTrace result + maybeReason <- checkExpectation diffmode x result + liftIO $ forM_ maybeReason assertFailure -- | Run a vm test and output a geth style per opcode trace @@ -225,13 +225,15 @@ checkStateFail diff x vm (okMoney, okNonce, okData, okCode) = do printContracts actual pure (unwords reason) -checkExpectation :: Bool -> Case -> VM RealWorld -> IO (Maybe String) +checkExpectation + :: (MonadUnliftIO m, ReadConfig m) + => Bool -> Case -> VM RealWorld -> m (Maybe String) checkExpectation diff x vm = do let expectation = x.testExpectation (okState, b2, b3, b4, b5) = checkExpectedContracts vm expectation if okState then pure Nothing - else + else liftIO $ Just <$> checkStateFail diff x vm (b2, b3, b4, b5) -- quotient account state by nullness diff --git a/test/test.hs b/test/test.hs index 458fe9586..6b6a10956 100644 --- a/test/test.hs +++ b/test/test.hs @@ -3207,6 +3207,7 @@ loadVM x = do Just (VMSuccess (ConcreteBuf targetCode)) -> do let target = vm1.state.contract vm2 <- Stepper.interpret (Fetch.zero 0 Nothing) vm1 (prepVm target targetCode >> Stepper.run) + writeTrace vm2 pure $ Just vm2 _ -> pure Nothing where From 6456455b1b2e431ae8984d1c10b53a565f44c6e1 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Fri, 13 Oct 2023 12:35:06 +0200 Subject: [PATCH 14/28] Fixing imports --- cli/cli.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/cli.hs b/cli/cli.hs index b3bd26757..7a08662f7 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -23,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(..)) @@ -36,7 +35,7 @@ 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 From 0aa1d089cd7c8674b275f47b44a7a078184fd717 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Fri, 13 Oct 2023 12:58:27 +0200 Subject: [PATCH 15/28] Refactor to make it more readable by d-xo --- bench/bench.hs | 9 +++----- cli/cli.hs | 10 ++++----- src/EVM/Effects.hs | 51 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/bench/bench.hs b/bench/bench.hs index 30cd890b5..e248ad8f2 100644 --- a/bench/bench.hs +++ b/bench/bench.hs @@ -27,9 +27,6 @@ import qualified EVM.Fetch as Fetch import EVM.Test.BlockchainTests qualified as BCTests -benchEnv :: Env -benchEnv = Env { config = defaultConfig } - main :: IO () main = defaultMain [ mkbench erc20 "erc20" 0 [1] @@ -72,7 +69,7 @@ blockchainTests ts = bench "blockchain-tests" $ nfIO $ do if n `elem` ignored then pure True else do - res <- runEnv benchEnv $ runBCTest c + res <- runApp $ runBCTest c pure $ acc && res ) True cases @@ -114,8 +111,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 $ runEnv benchEnv $ findPanics CVC5 i iters c' - , bench ("z3-" <> show i) $ nfIO $ runEnv benchEnv $ 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 ] diff --git a/cli/cli.hs b/cli/cli.hs index 7a08662f7..f2facdc8a 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -184,17 +184,15 @@ getFullVersion = showVersion Paths.version <> " [" <> gitVersion <> "]" Right val -> "git rev " <> giBranch val <> "@" <> giHash val Left _ -> "no git revision present" -mainEnv :: Env -mainEnv = Env { config = defaultConfig } - main :: IO () main = do cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator" - let env = Env { config = defaultConfig { - dumpQueries = cmd.smtdebug + let env = Env { config = defaultConfig + { dumpQueries = cmd.smtdebug , abstRefineMem = cmd.abstractMemory , abstRefineArith = cmd.abstractArithmetic - , dumpTrace = cmd.trace } } + , dumpTrace = cmd.trace + } } case cmd of Version {} ->putStrLn getFullVersion Symbolic {} -> do diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs index 08672ac53..c243807c3 100644 --- a/src/EVM/Effects.hs +++ b/src/EVM/Effects.hs @@ -1,3 +1,19 @@ +{-| +Module : Effects +Description : Domain specific effects + +This module contains custom app specific mtl style effects for hevm +These are written in the style of the ReaderT over IO pattern [1]. +Right now we only have a single `ReadConfig` effect, but over time hope to +migrate most usages of IO into custom effects here. + +This framework would allow us to have multiple interpretations for effects +(e.g. a pure version for tests), but for now we interpret everything in IO +only. + +[1]: https://www.fpcomplete.com/blog/readert-design-pattern/ +-} + {-# Language RankNTypes #-} {-# Language FlexibleInstances #-} {-# Language KindSignatures #-} @@ -6,6 +22,7 @@ {-# Language DerivingStrategies #-} {-# Language DuplicateRecordFields #-} {-# Language NoFieldSelectors #-} +{-# Language ConstraintKinds #-} module EVM.Effects where @@ -14,14 +31,17 @@ import Control.Monad.IO.Unlift import EVM.Dapp (DappInfo) import EVM.Types (VM(..)) import Control.Monad.ST (RealWorld) -import Data.Text.IO as T +import Data.Text.IO qualified as T import EVM.Format (showTraceTree) import EVM (traceForest) -- Abstract Effects -------------------------------------------------------------------------------- -- Here we define the abstract interface for the effects that we wish to model --- This is a concrete datatype that contains handlers for the above effects inside the IO monad. +-- Global config +class Monad m => ReadConfig m where + readConfig :: m Config + data Config = Config { dumpQueries :: Bool , dumpExprs :: Bool @@ -34,8 +54,8 @@ data Config = Config deriving (Show, Eq) defaultConfig :: Config -defaultConfig = Config { - dumpQueries = False +defaultConfig = Config + { dumpQueries = False , dumpExprs = False , dumpEndStates = False , verbose = False @@ -56,21 +76,28 @@ writeTrace => VM RealWorld -> m () writeTrace vm = do conf <- readConfig - liftIO $ when conf.dumpTrace $ Prelude.writeFile "VM.trace" (show $ traceForest vm) + liftIO $ when conf.dumpTrace $ writeFile "VM.trace" (show $ traceForest vm) +-- IO Interpretation ------------------------------------------------------------------------------- -data Env = Env +newtype Env = Env { config :: Config } --- forall {r} {m :: Type -> Type} {a}. r -> ReaderT r m a -> m a -runEnv :: Env -> ReaderT Env m a -> m a -runEnv e a = runReaderT a e - -class Monad m => ReadConfig m where - readConfig :: m Config +defaultEnv :: Env +defaultEnv = Env { config = defaultConfig } instance Monad m => ReadConfig (ReaderT Env m) where readConfig = do e <- ask pure e.config + +runEnv :: Env -> ReaderT Env m a -> m a +runEnv e a = runReaderT a e + +-- Helpful Aliases --------------------------------------------------------------------------------- + +type App m = (MonadUnliftIO m, ReadConfig m) + +runApp :: ReaderT Env m a -> m a +runApp = runEnv defaultEnv From 69c391f08e84a32df848695eea9307741df91937 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Fri, 13 Oct 2023 13:04:04 +0200 Subject: [PATCH 16/28] Cleaner monadic patterns --- bench/bench.hs | 4 ++-- cli/cli.hs | 8 ++++---- src/EVM/Effects.hs | 4 ++-- src/EVM/Fetch.hs | 2 +- src/EVM/Solvers.hs | 4 ++-- src/EVM/Stepper.hs | 2 +- src/EVM/SymExec.hs | 20 ++++++++++---------- src/EVM/UnitTest.hs | 6 +++--- test/EVM/Test/BlockchainTests.hs | 12 ++++++------ test/EVM/Test/Tracing.hs | 16 ++++++++-------- test/EVM/Test/Utils.hs | 4 ++-- test/test.hs | 24 ++++++++++++------------ 12 files changed, 53 insertions(+), 53 deletions(-) diff --git a/bench/bench.hs b/bench/bench.hs index e248ad8f2..f6263ecae 100644 --- a/bench/bench.hs +++ b/bench/bench.hs @@ -75,7 +75,7 @@ blockchainTests ts = bench "blockchain-tests" $ nfIO $ do -- | executes a single test case and returns a boolean value representing its success runBCTest - :: (MonadUnliftIO m, ReadConfig m) + :: App m => BCTests.Case -> m Bool runBCTest x = do @@ -91,7 +91,7 @@ runBCTest x = findPanics - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Solver -> Natural -> Integer -> ByteString -> m () findPanics solver count iters c = do _ <- withSolvers solver count Nothing $ \s -> do diff --git a/cli/cli.hs b/cli/cli.hs index f2facdc8a..89e616c9b 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -218,7 +218,7 @@ main = do liftIO $ unless res exitFailure equivalence - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Command Options.Unwrapped -> m () equivalence cmd = do let bytecodeA = hexByteString "--code" . strip0x $ cmd.codeA @@ -296,7 +296,7 @@ buildCalldata cmd = case (cmd.calldata, cmd.sig) of -- If function signatures are known, they should always be given for best results. assert - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Command Options.Unwrapped -> m () assert cmd = do let block' = maybe Fetch.Latest Fetch.BlockNumber cmd.block @@ -341,7 +341,7 @@ assert cmd = do liftIO $ exitFailure showExtras - :: (MonadUnliftIO m, ReadConfig m) + :: App m => SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> m () showExtras solvers cmd calldata expr = do when cmd.showTree $ liftIO $ do @@ -366,7 +366,7 @@ areAnyPrefixOf :: [Text] -> Text -> Bool areAnyPrefixOf prefixes t = any (flip T.isPrefixOf t) prefixes launchExec - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Command Options.Unwrapped -> m () launchExec cmd = do dapp <- liftIO $ getSrcInfo cmd diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs index c243807c3..5f47b687c 100644 --- a/src/EVM/Effects.hs +++ b/src/EVM/Effects.hs @@ -65,14 +65,14 @@ defaultConfig = Config } writeTraceDapp - :: (MonadUnliftIO m, ReadConfig m) + :: App m => DappInfo -> VM RealWorld -> m () writeTraceDapp dapp vm = do conf <- readConfig liftIO $ when conf.dumpTrace $ T.writeFile "VM.trace" (showTraceTree dapp vm) writeTrace - :: (MonadUnliftIO m, ReadConfig m) + :: App m => VM RealWorld -> m () writeTrace vm = do conf <- readConfig diff --git a/src/EVM/Fetch.hs b/src/EVM/Fetch.hs index c9763484f..08bfe660e 100644 --- a/src/EVM/Fetch.hs +++ b/src/EVM/Fetch.hs @@ -239,7 +239,7 @@ type Fetcher m s = (ReadConfig m, MonadUnliftIO m) => Query s -> m (EVM s ()) -- When in debug mode, we do not want to be able to navigate to dead paths, -- but for normal execution paths with inconsistent pathconditions -- will be pruned anyway. -checkBranch :: (MonadUnliftIO m, ReadConfig m) => SolverGroup -> Prop -> Prop -> m BranchCondition +checkBranch :: App m => SolverGroup -> Prop -> Prop -> m BranchCondition checkBranch solvers branchcondition pathconditions = do conf <- readConfig liftIO $ checkSat solvers (assertProps conf [(branchcondition .&& pathconditions)]) >>= \case diff --git a/src/EVM/Solvers.hs b/src/EVM/Solvers.hs index f802b76ff..57b2daae2 100644 --- a/src/EVM/Solvers.hs +++ b/src/EVM/Solvers.hs @@ -96,7 +96,7 @@ writeSMT2File smt2 count abst = let content = formatSMT2 smt2 <> "\n\n(check-sat)" T.writeFile ("query-" <> (show count) <> "-" <> abst <> ".smt2") content -withSolvers :: (MonadUnliftIO m, ReadConfig m) => Solver -> Natural -> Maybe Natural -> (SolverGroup -> m a) -> m a +withSolvers :: App m => Solver -> Natural -> Maybe Natural -> (SolverGroup -> m a) -> m a withSolvers solver count timeout cont = do -- spawn solvers instances <- mapM (const $ liftIO $ spawnSolver solver timeout) [1..count] @@ -115,7 +115,7 @@ withSolvers solver count timeout cont = do liftIO $ killThread orchestrateId pure res where - orchestrate :: (MonadUnliftIO m, ReadConfig m) => Chan Task -> Chan SolverInstance -> Int -> m b + orchestrate :: App m => Chan Task -> Chan SolverInstance -> Int -> m b orchestrate queue avail fileCounter = do task <- liftIO $ readChan queue inst <- liftIO $ readChan avail diff --git a/src/EVM/Stepper.hs b/src/EVM/Stepper.hs index b942b5839..f11f5ea0a 100644 --- a/src/EVM/Stepper.hs +++ b/src/EVM/Stepper.hs @@ -110,7 +110,7 @@ runFully = do enter :: Text -> Stepper s () enter t = evm (EVM.pushTrace (EntryTrace t)) -interpret :: forall m a . (ReadConfig m, MonadUnliftIO m) => Fetch.Fetcher m RealWorld -> VM RealWorld -> Stepper RealWorld a -> m a +interpret :: forall m a . (App m) => Fetch.Fetcher m RealWorld -> VM RealWorld -> Stepper RealWorld a -> m a interpret fetcher vm = eval . view where eval :: ProgramView (Action RealWorld) a -> m a diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs index 882df4e8f..b3510987e 100644 --- a/src/EVM/SymExec.hs +++ b/src/EVM/SymExec.hs @@ -232,7 +232,7 @@ loadSymVM x callvalue cd create = -- | Interpreter which explores all paths at branching points. Returns an -- 'Expr End' representing the possible executions. interpret - :: forall m . (MonadUnliftIO m, ReadConfig m) + :: forall m . App m => Fetch.Fetcher m RealWorld -> Maybe Integer -- max iterations -> Integer -- ask smt iterations @@ -356,7 +356,7 @@ type Precondition s = VM s -> Prop type Postcondition s = VM s -> Expr End -> Prop checkAssert - :: (MonadUnliftIO m, ReadConfig m) + :: App m => SolverGroup -> [Word256] -> ByteString @@ -368,7 +368,7 @@ checkAssert solvers errs c signature' concreteArgs opts = verifyContract solvers c signature' concreteArgs opts Nothing (Just $ checkAssertions errs) getExpr - :: (MonadUnliftIO m, ReadConfig m) + :: App m => SolverGroup -> ByteString -> Maybe Sig @@ -431,7 +431,7 @@ mkCalldata (Just (Sig name types)) args = symCalldata name types args (AbstractBuf "txdata") verifyContract - :: (MonadUnliftIO m, ReadConfig m) + :: App m => SolverGroup -> ByteString -> Maybe Sig @@ -482,7 +482,7 @@ flattenExpr = go [] -- incremental queries might let us go even faster here. -- TODO: handle errors properly reachable - :: (MonadUnliftIO m, ReadConfig m) + :: App m => SolverGroup -> Expr End -> m ([SMT2], Expr End) reachable solvers e = do conf <- readConfig @@ -539,7 +539,7 @@ getPartials = mapMaybe go -- | Symbolically execute the VM and check all endstates against the -- postcondition, if available. verify - :: (MonadUnliftIO m, ReadConfig m) + :: App m => SolverGroup -> VeriOpts -> VM RealWorld @@ -616,7 +616,7 @@ type UnsatCache = TVar [Set Prop] -- equivalence break, and since we run this check for every pair of end states, -- the check is exhaustive. equivalenceCheck - :: forall m . (MonadUnliftIO m, ReadConfig m) + :: forall m . App m => SolverGroup -> ByteString -> ByteString @@ -644,7 +644,7 @@ equivalenceCheck solvers bytecodeA bytecodeB opts calldata = do equivalenceCheck' - :: forall m . (MonadUnliftIO m, ReadConfig m) + :: forall m . App m => SolverGroup -> [Expr End] -> [Expr End] -> m [EquivResult] equivalenceCheck' solvers branchesA branchesB = do when (any isPartial branchesA || any isPartial branchesB) $ liftIO $ do @@ -714,7 +714,7 @@ equivalenceCheck' solvers branchesA branchesB = do -- mapConcurrently which would spawn as many threads as there are jobs, and -- run them in a random order. We ordered them correctly, though so that'd be bad checkAll - :: (MonadUnliftIO m, ReadConfig m) + :: App m => [(Set Prop)] -> UnsatCache -> Int -> m [(EquivResult, Bool)] checkAll input cache numproc = do conf <- readConfig @@ -787,7 +787,7 @@ both' :: (a -> b) -> (a, a) -> (b, b) both' f (x, y) = (f x, f y) produceModels - :: (MonadUnliftIO m, ReadConfig m) + :: App m => SolverGroup -> Expr End -> m [(Expr End, CheckSatResult)] produceModels solvers expr = do let flattened = flattenExpr expr diff --git a/src/EVM/UnitTest.hs b/src/EVM/UnitTest.hs index 73cc81a73..f3b910374 100644 --- a/src/EVM/UnitTest.hs +++ b/src/EVM/UnitTest.hs @@ -104,7 +104,7 @@ makeVeriOpts opts = -- | Top level CLI endpoint for hevm test unitTest - :: (MonadUnliftIO m, ReadConfig m) + :: App m => UnitTestOptions RealWorld -> Contracts -> m Bool unitTest opts (Contracts cs) = do let unitTests = findUnitTests opts.match $ Map.elems cs @@ -145,7 +145,7 @@ initializeUnitTest opts theContract = do _ -> popTrace runUnitTestContract - :: (MonadUnliftIO m, ReadConfig m) + :: App m => UnitTestOptions RealWorld -> Map Text SolcContract -> (Text, [Sig]) @@ -200,7 +200,7 @@ runUnitTestContract -- | Define the thread spawner for symbolic tests symRun - :: (MonadUnliftIO m, ReadConfig m) + :: App m => UnitTestOptions RealWorld -> VM RealWorld -> Sig -> m (Text, Either Text Text) symRun opts@UnitTestOptions{..} vm (Sig testName types) = do let cd = symCalldata testName types [] (AbstractBuf "txdata") diff --git a/test/EVM/Test/BlockchainTests.hs b/test/EVM/Test/BlockchainTests.hs index 74cfa17dc..1e06d5e10 100644 --- a/test/EVM/Test/BlockchainTests.hs +++ b/test/EVM/Test/BlockchainTests.hs @@ -73,7 +73,7 @@ main = do tests <- runEnv testEnv prepareTests defaultMain tests -prepareTests :: (MonadUnliftIO m, ReadConfig m) => m TestTree +prepareTests :: App m => m TestTree prepareTests = do repo <- liftIO $ getEnv "HEVM_ETHEREUM_TESTS_REPO" let testsDir = "BlockchainTests/GeneralStateTests" @@ -88,7 +88,7 @@ prepareTests = do pure $ testGroup "ethereum-tests" groups testsFromFile - :: forall m . (MonadUnliftIO m, ReadConfig m) + :: forall m . App m => String -> Map String (TestTree -> TestTree) -> m [TestTree] testsFromFile file problematicTests = do parsed <- parseBCSuite <$> (liftIO $ LazyByteString.readFile file) @@ -139,7 +139,7 @@ ciProblematicTests = Map.fromList ] runVMTest - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Bool -> (String, Case) -> m () runVMTest diffmode (_name, x) = do vm0 <- liftIO $ vmForCase x @@ -151,7 +151,7 @@ runVMTest diffmode (_name, x) = do -- | Run a vm test and output a geth style per opcode trace traceVMTest - :: (MonadUnliftIO m, ReadConfig m) + :: App m => String -> String -> m [VMTrace] traceVMTest file test = do repo <- liftIO $ getEnv "HEVM_ETHEREUM_TESTS_REPO" @@ -174,7 +174,7 @@ readTrace = JSON.eitherDecodeFileStrict -- | given a path to a test file, a test case from within that file, and a trace from geth from running that test, compare the traces and show where we differ -- This would need a few tweaks to geth to make this really usable (i.e. evm statetest show allow running a single test from within the test file). traceVsGeth - :: (MonadUnliftIO m, ReadConfig m) + :: App m => String -> String -> FilePath -> m () traceVsGeth file test gethTrace = do hevm <- traceVMTest file test @@ -226,7 +226,7 @@ checkStateFail diff x vm (okMoney, okNonce, okData, okCode) = do pure (unwords reason) checkExpectation - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Bool -> Case -> VM RealWorld -> m (Maybe String) checkExpectation diff x vm = do let expectation = x.testExpectation diff --git a/test/EVM/Test/Tracing.hs b/test/EVM/Test/Tracing.hs index f6a698710..65fb474e2 100644 --- a/test/EVM/Test/Tracing.hs +++ b/test/EVM/Test/Tracing.hs @@ -300,7 +300,7 @@ evmSetup contr txData gaslimitExec = (txn, evmEnv, contrAlloc, fromAddress, toAd toAddress = 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 getHEVMRet - :: (MonadUnliftIO m, ReadConfig m) + :: App m => OpContract -> ByteString -> Int -> m (Either (EvmError, [VMTrace]) (Expr 'End, [VMTrace], VMTraceResult)) getHEVMRet contr txData gaslimitExec = do let (txn, evmEnv, contrAlloc, fromAddress, toAddress, _) = evmSetup contr txData gaslimitExec @@ -420,7 +420,7 @@ symbolify vm = vm { state = vm.state { calldata = AbstractBuf "calldata" } } -- | Takes a runtime code and calls it with the provided calldata -- Uses evmtool's alloc and transaction to set up the VM correctly runCodeWithTrace - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Fetch.RpcInfo -> EVMToolEnv -> EVMToolAlloc -> EVM.Transaction.Transaction -> Expr EAddr -> Expr EAddr -> m (Either (EvmError, [VMTrace]) ((Expr 'End, [VMTrace], VMTraceResult))) runCodeWithTrace rpcinfo evmEnv alloc txn fromAddr toAddress = withSolvers Z3 0 Nothing $ \solvers -> do @@ -469,7 +469,7 @@ vmForRuntimeCode runtimecode calldata' evmToolEnv alloc txn fromAddr toAddress = <&> set (#state % #calldata) calldata' runCode - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Fetch.RpcInfo -> ByteString -> Expr Buf -> m (Maybe (Expr Buf)) runCode rpcinfo code' calldata' = withSolvers Z3 0 Nothing $ \solvers -> do origVM <- liftIO $ stToIO $ vmForRuntimeCode @@ -541,14 +541,14 @@ vmres vm = type TraceState s = (VM s, [VMTrace]) execWithTrace - :: (MonadUnliftIO m, ReadConfig m) + :: App m => StateT (TraceState RealWorld) m (VMResult RealWorld) execWithTrace = do _ <- runWithTrace fromJust <$> use (_1 % #result) runWithTrace - :: (MonadUnliftIO m, ReadConfig m) + :: App m => StateT (TraceState RealWorld) m (VM RealWorld) runWithTrace = do -- This is just like `exec` except for every instruction evaluated, @@ -570,7 +570,7 @@ runWithTrace = do Just _ -> pure vm0 interpretWithTrace - :: forall m a . (MonadUnliftIO m, ReadConfig m) + :: forall m a . App m => Fetch.Fetcher m RealWorld -> Stepper.Stepper RealWorld a -> StateT (TraceState RealWorld) m a @@ -578,7 +578,7 @@ interpretWithTrace fetcher = eval . Operational.view where eval - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Operational.ProgramView (Stepper.Action RealWorld) a -> StateT (TraceState RealWorld) m a eval (Operational.Return x) = pure x @@ -889,7 +889,7 @@ tests = testGroup "contract-quickcheck-run" ] checkTraceAndOutputs - :: (MonadUnliftIO m, ReadConfig m) + :: App m => OpContract -> Int -> ByteString -> m () checkTraceAndOutputs contract gasLimit txData = do evmtoolResult <- liftIO $ getEVMToolRet contract txData gasLimit diff --git a/test/EVM/Test/Utils.hs b/test/EVM/Test/Utils.hs index c62dc1f15..6374db06b 100644 --- a/test/EVM/Test/Utils.hs +++ b/test/EVM/Test/Utils.hs @@ -25,7 +25,7 @@ import Control.Monad.Catch (MonadMask) import EVM.Effects runSolidityTestCustom - :: (MonadMask m, MonadUnliftIO m, ReadConfig m) + :: (MonadMask m, App m) => FilePath -> Text -> Maybe Natural -> Maybe Integer -> Bool -> RpcInfo -> ProjectType -> m Bool runSolidityTestCustom testFile match timeout maxIter ffiAllowed rpcinfo projectType = do withSystemTempDirectory "dapp-test" $ \root -> do @@ -37,7 +37,7 @@ runSolidityTestCustom testFile match timeout maxIter ffiAllowed rpcinfo projectT unitTest opts contracts runSolidityTest - :: (MonadMask m, MonadUnliftIO m, ReadConfig m) + :: (MonadMask m, App m) => FilePath -> Text -> m Bool runSolidityTest testFile match = runSolidityTestCustom testFile match Nothing Nothing True Nothing Foundry diff --git a/test/test.hs b/test/test.hs index 6b6a10956..50eeb7ab4 100644 --- a/test/test.hs +++ b/test/test.hs @@ -80,7 +80,7 @@ putStrLnM :: (MonadUnliftIO m) => String -> m () putStrLnM a = liftIO $ putStrLn a assertEqualM - :: (MonadUnliftIO m, ReadConfig m, Eq a, Show a, HasCallStack) + :: (App m, Eq a, Show a, HasCallStack) => String -> a -> a -> m () assertEqualM a b c = liftIO $ assertEqual a b c @@ -3150,17 +3150,17 @@ tests = testGroup "hevm" (===>) = assertSolidityComputation checkEquivProp - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Prop -> Prop -> m Bool checkEquivProp = checkEquivBase (\l r -> PNeg (PImpl l r .&& PImpl r l)) checkEquiv - :: (Typeable a, MonadUnliftIO m, ReadConfig m) + :: (Typeable a, App m) => Expr a -> Expr a -> m Bool checkEquiv = checkEquivBase (./=) checkEquivBase - :: (Eq a, MonadUnliftIO m, ReadConfig m) + :: (Eq a, App m) => (a -> a -> Prop) -> a -> a -> m Bool checkEquivBase mkprop l r = do conf <- readConfig @@ -3183,7 +3183,7 @@ checkEquivBase mkprop l r = do -- | Takes a creation code and some calldata, runs the creation code, and calls the resulting contract with the provided calldata runSimpleVM - :: (MonadUnliftIO m, ReadConfig m) + :: App m => ByteString -> ByteString -> m (Maybe ByteString) runSimpleVM x ins = do loadVM x >>= \case @@ -3198,7 +3198,7 @@ runSimpleVM x ins = do -- | Takes a creation code and returns a vm with the result of executing the creation code loadVM - :: (MonadUnliftIO m, ReadConfig m) + :: App m => ByteString -> m (Maybe (VM RealWorld)) loadVM x = do vm <- liftIO $ stToIO $ vmForEthrunCreation x @@ -3242,14 +3242,14 @@ defaultDataLocation t = else "" runFunction - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Text -> ByteString -> m (Maybe ByteString) runFunction c input = do x <- liftIO $ singleContract "X" c runSimpleVM (fromJust x) input runStatements - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Text -> [AbiValue] -> AbiType -> m (Maybe ByteString) runStatements stmts args t = do @@ -3827,7 +3827,7 @@ data Invocation deriving Show assertSolidityComputation - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Invocation -> AbiValue -> m () assertSolidityComputation (SolidityCall s args) x = do y <- runStatements s args (abiValueType x) @@ -3850,7 +3850,7 @@ checkBadCheatCode sig _ = \case _ -> PBool True allBranchesFail - :: (MonadUnliftIO m, ReadConfig m) + :: App m => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) allBranchesFail = checkPost (Just p) where @@ -3859,12 +3859,12 @@ allBranchesFail = checkPost (Just p) _ -> PBool True reachableUserAsserts - :: (MonadUnliftIO m, ReadConfig m) + :: App m => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) reachableUserAsserts = checkPost (Just $ checkAssertions [0x01]) checkPost - :: (MonadUnliftIO m, ReadConfig m) + :: App m => Maybe (Postcondition RealWorld) -> ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) checkPost post c sig = do (e, res) <- withSolvers Z3 1 Nothing $ \s -> From 072821f40b33687d6e842292e55d76d52c6395e9 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Fri, 13 Oct 2023 14:13:07 +0200 Subject: [PATCH 17/28] Removing useless imports --- src/EVM/Stepper.hs | 1 - src/EVM/UnitTest.hs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/EVM/Stepper.hs b/src/EVM/Stepper.hs index f11f5ea0a..591d50ec9 100644 --- a/src/EVM/Stepper.hs +++ b/src/EVM/Stepper.hs @@ -36,7 +36,6 @@ import EVM.Types import Control.Monad.ST (stToIO, RealWorld) import Control.Monad.IO.Class import EVM.Effects -import Control.Monad.IO.Unlift -- | The instruction type of the operational monad data Action s a where diff --git a/src/EVM/UnitTest.hs b/src/EVM/UnitTest.hs index f3b910374..ea21e9505 100644 --- a/src/EVM/UnitTest.hs +++ b/src/EVM/UnitTest.hs @@ -44,7 +44,6 @@ import GHC.Natural import System.IO (hFlush, stdout) import Witch (unsafeInto, into) import EVM.Effects -import Control.Monad.IO.Unlift data UnitTestOptions s = UnitTestOptions { rpcInfo :: Fetch.RpcInfo From bcd8573b52ceece56124ef19238e3226445cd6e5 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 16 Oct 2023 12:58:56 +0200 Subject: [PATCH 18/28] All config options in test.hs --- test/test.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test.hs b/test/test.hs index 50eeb7ab4..1681c5abe 100644 --- a/test/test.hs +++ b/test/test.hs @@ -74,6 +74,10 @@ testEnv = Env { config = defaultConfig { dumpQueries = False , dumpExprs = False , dumpEndStates = False + , verbose = False + , abstRefineArith = False + , abstRefineMem = False + , dumpTrace = False } } putStrLnM :: (MonadUnliftIO m) => String -> m () From f16c8f64585b00e2608a78687d1d6c68e0bae2c8 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Mon, 16 Oct 2023 13:10:49 +0200 Subject: [PATCH 19/28] Cleanup --- bench/bench.hs | 8 ++----- cli/cli.hs | 16 ++++--------- src/EVM/Effects.hs | 8 ++----- src/EVM/SymExec.hs | 12 +++------- src/EVM/UnitTest.hs | 8 ++----- test/EVM/Test/Tracing.hs | 16 ++++--------- test/test.hs | 51 ++++++++++------------------------------ 7 files changed, 30 insertions(+), 89 deletions(-) diff --git a/bench/bench.hs b/bench/bench.hs index f6263ecae..d5a02a9e1 100644 --- a/bench/bench.hs +++ b/bench/bench.hs @@ -74,9 +74,7 @@ blockchainTests ts = bench "blockchain-tests" $ nfIO $ do ) True cases -- | executes a single test case and returns a boolean value representing its success -runBCTest - :: App m - => BCTests.Case -> m Bool +runBCTest :: App m => BCTests.Case -> m Bool runBCTest x = do vm0 <- liftIO $ BCTests.vmForCase x @@ -90,9 +88,7 @@ runBCTest x = --- Helpers ---------------------------------------------------------------------------------------- -findPanics - :: App m - => Solver -> Natural -> Integer -> ByteString -> m () +findPanics :: App m => Solver -> Natural -> Integer -> ByteString -> m () findPanics solver count iters c = do _ <- withSolvers solver count Nothing $ \s -> do let opts = defaultVeriOpts diff --git a/cli/cli.hs b/cli/cli.hs index 89e616c9b..aebbb45f8 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -217,9 +217,7 @@ main = do res <- unitTest testOpts out.contracts liftIO $ unless res exitFailure -equivalence - :: App m - => Command Options.Unwrapped -> m () +equivalence :: App m => Command Options.Unwrapped -> m () equivalence cmd = do let bytecodeA = hexByteString "--code" . strip0x $ cmd.codeA bytecodeB = hexByteString "--code" . strip0x $ cmd.codeB @@ -295,9 +293,7 @@ buildCalldata cmd = case (cmd.calldata, cmd.sig) of -- If function signatures are known, they should always be given for best results. -assert - :: App m - => Command Options.Unwrapped -> m () +assert :: App m => Command Options.Unwrapped -> m () assert cmd = do let block' = maybe Fetch.Latest Fetch.BlockNumber cmd.block rpcinfo = (,) block' <$> cmd.rpc @@ -340,9 +336,7 @@ assert cmd = do showExtras solvers cmd calldata expr liftIO $ exitFailure -showExtras - :: App m - => SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> m () +showExtras :: App m => SolverGroup -> Command Options.Unwrapped -> (Expr Buf, [Prop]) -> Expr End -> m () showExtras solvers cmd calldata expr = do when cmd.showTree $ liftIO $ do putStrLn "=== Expression ===\n" @@ -365,9 +359,7 @@ isTestOrLib file = T.isSuffixOf ".t.sol" file || areAnyPrefixOf ["src/test/", "s areAnyPrefixOf :: [Text] -> Text -> Bool areAnyPrefixOf prefixes t = any (flip T.isPrefixOf t) prefixes -launchExec - :: App m - => Command Options.Unwrapped -> m () +launchExec :: App m => Command Options.Unwrapped -> m () launchExec cmd = do dapp <- liftIO $ getSrcInfo cmd vm <- liftIO $ vmFromCommand cmd diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs index 5f47b687c..09a31e46d 100644 --- a/src/EVM/Effects.hs +++ b/src/EVM/Effects.hs @@ -64,16 +64,12 @@ defaultConfig = Config , dumpTrace = False } -writeTraceDapp - :: App m - => DappInfo -> VM RealWorld -> m () +writeTraceDapp :: App m => DappInfo -> VM RealWorld -> m () writeTraceDapp dapp vm = do conf <- readConfig liftIO $ when conf.dumpTrace $ T.writeFile "VM.trace" (showTraceTree dapp vm) -writeTrace - :: App m - => VM RealWorld -> m () +writeTrace :: App m => VM RealWorld -> m () writeTrace vm = do conf <- readConfig liftIO $ when conf.dumpTrace $ writeFile "VM.trace" (show $ traceForest vm) diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs index b3510987e..dd18a064a 100644 --- a/src/EVM/SymExec.hs +++ b/src/EVM/SymExec.hs @@ -481,9 +481,7 @@ flattenExpr = go [] -- the incremental nature of the task at hand. Introducing support for -- incremental queries might let us go even faster here. -- TODO: handle errors properly -reachable - :: App m - => SolverGroup -> Expr End -> m ([SMT2], Expr End) +reachable :: App m => SolverGroup -> Expr End -> m ([SMT2], Expr End) reachable solvers e = do conf <- readConfig res <- liftIO $ go conf [] e @@ -713,9 +711,7 @@ equivalenceCheck' solvers branchesA branchesB = do -- from left-to-right, and with a max of K threads. This is in contrast to -- mapConcurrently which would spawn as many threads as there are jobs, and -- run them in a random order. We ordered them correctly, though so that'd be bad - checkAll - :: App m - => [(Set Prop)] -> UnsatCache -> Int -> m [(EquivResult, Bool)] + checkAll :: App m => [(Set Prop)] -> UnsatCache -> Int -> m [(EquivResult, Bool)] checkAll input cache numproc = do conf <- readConfig wrap <- liftIO $ pool numproc @@ -786,9 +782,7 @@ equivalenceCheck' solvers branchesA branchesB = do both' :: (a -> b) -> (a, a) -> (b, b) both' f (x, y) = (f x, f y) -produceModels - :: App m - => SolverGroup -> Expr End -> m [(Expr End, CheckSatResult)] +produceModels :: App m => SolverGroup -> Expr End -> m [(Expr End, CheckSatResult)] produceModels solvers expr = do let flattened = flattenExpr expr withQueries conf = fmap (\e -> ((assertProps conf) . extractProps $ e, e)) flattened diff --git a/src/EVM/UnitTest.hs b/src/EVM/UnitTest.hs index ea21e9505..4267f5c99 100644 --- a/src/EVM/UnitTest.hs +++ b/src/EVM/UnitTest.hs @@ -102,9 +102,7 @@ makeVeriOpts opts = } -- | Top level CLI endpoint for hevm test -unitTest - :: App m - => UnitTestOptions RealWorld -> Contracts -> m Bool +unitTest :: App m => UnitTestOptions RealWorld -> Contracts -> m Bool unitTest opts (Contracts cs) = do let unitTests = findUnitTests opts.match $ Map.elems cs results <- concatMapM (runUnitTestContract opts cs) unitTests @@ -198,9 +196,7 @@ runUnitTestContract -- | Define the thread spawner for symbolic tests -symRun - :: App m - => UnitTestOptions RealWorld -> VM RealWorld -> Sig -> m (Text, Either Text Text) +symRun :: App m => UnitTestOptions RealWorld -> VM RealWorld -> Sig -> m (Text, Either Text Text) symRun opts@UnitTestOptions{..} vm (Sig testName types) = do let cd = symCalldata testName types [] (AbstractBuf "txdata") shouldFail = "proveFail" `isPrefixOf` testName diff --git a/test/EVM/Test/Tracing.hs b/test/EVM/Test/Tracing.hs index 65fb474e2..f378f6d0f 100644 --- a/test/EVM/Test/Tracing.hs +++ b/test/EVM/Test/Tracing.hs @@ -468,9 +468,7 @@ vmForRuntimeCode runtimecode calldata' evmToolEnv alloc txn fromAddr toAddress = (Just (initialContract (RuntimeCode (ConcreteRuntimeCode BS.empty)))) <&> set (#state % #calldata) calldata' -runCode - :: App m - => Fetch.RpcInfo -> ByteString -> Expr Buf -> m (Maybe (Expr Buf)) +runCode :: App m => Fetch.RpcInfo -> ByteString -> Expr Buf -> m (Maybe (Expr Buf)) runCode rpcinfo code' calldata' = withSolvers Z3 0 Nothing $ \solvers -> do origVM <- liftIO $ stToIO $ vmForRuntimeCode code' @@ -540,16 +538,12 @@ vmres vm = type TraceState s = (VM s, [VMTrace]) -execWithTrace - :: App m - => StateT (TraceState RealWorld) m (VMResult RealWorld) +execWithTrace :: App m => StateT (TraceState RealWorld) m (VMResult RealWorld) execWithTrace = do _ <- runWithTrace fromJust <$> use (_1 % #result) -runWithTrace - :: App m - => StateT (TraceState RealWorld) m (VM RealWorld) +runWithTrace :: App m => StateT (TraceState RealWorld) m (VM RealWorld) runWithTrace = do -- This is just like `exec` except for every instruction evaluated, -- we also increment a counter indexed by the current code location. @@ -888,9 +882,7 @@ tests = testGroup "contract-quickcheck-run" checkTraceAndOutputs contract 40000 (BS.pack [1, 2, 3, 4, 5, 6]) ] -checkTraceAndOutputs - :: App m - => OpContract -> Int -> ByteString -> m () +checkTraceAndOutputs :: App m => OpContract -> Int -> ByteString -> m () checkTraceAndOutputs contract gasLimit txData = do evmtoolResult <- liftIO $ getEVMToolRet contract txData gasLimit hevmRun <- getHEVMRet contract txData gasLimit diff --git a/test/test.hs b/test/test.hs index 1681c5abe..8a598e58f 100644 --- a/test/test.hs +++ b/test/test.hs @@ -83,9 +83,7 @@ testEnv = Env { config = defaultConfig { putStrLnM :: (MonadUnliftIO m) => String -> m () putStrLnM a = liftIO $ putStrLn a -assertEqualM - :: (App m, Eq a, Show a, HasCallStack) - => String -> a -> a -> m () +assertEqualM :: (App m, Eq a, Show a, HasCallStack) => String -> a -> a -> m () assertEqualM a b c = liftIO $ assertEqual a b c assertBoolM @@ -96,7 +94,7 @@ assertBoolM a b = liftIO $ assertBool a b test :: TestName -> ReaderT Env IO () -> TestTree test a b = testCase a $ runEnv testEnv b -prop:: Testable prop => ReaderT Env IO prop -> Property +prop :: Testable prop => ReaderT Env IO prop -> Property prop a = ioProperty $ runEnv testEnv a main :: IO () @@ -3153,19 +3151,13 @@ tests = testGroup "hevm" where (===>) = assertSolidityComputation -checkEquivProp - :: App m - => Prop -> Prop -> m Bool +checkEquivProp :: App m => Prop -> Prop -> m Bool checkEquivProp = checkEquivBase (\l r -> PNeg (PImpl l r .&& PImpl r l)) -checkEquiv - :: (Typeable a, App m) - => Expr a -> Expr a -> m Bool +checkEquiv :: (Typeable a, App m) => Expr a -> Expr a -> m Bool checkEquiv = checkEquivBase (./=) -checkEquivBase - :: (Eq a, App m) - => (a -> a -> Prop) -> a -> a -> m Bool +checkEquivBase :: (Eq a, App m) => (a -> a -> Prop) -> a -> a -> m Bool checkEquivBase mkprop l r = do conf <- readConfig withSolvers Z3 1 (Just 1) $ \solvers -> liftIO $ do @@ -3186,9 +3178,7 @@ checkEquivBase mkprop l r = do -- | Takes a runtime code and calls it with the provided calldata -- | Takes a creation code and some calldata, runs the creation code, and calls the resulting contract with the provided calldata -runSimpleVM - :: App m - => ByteString -> ByteString -> m (Maybe ByteString) +runSimpleVM :: App m => ByteString -> ByteString -> m (Maybe ByteString) runSimpleVM x ins = do loadVM x >>= \case Nothing -> pure Nothing @@ -3201,9 +3191,7 @@ runSimpleVM x ins = do s -> internalError $ show s -- | Takes a creation code and returns a vm with the result of executing the creation code -loadVM - :: App m - => ByteString -> m (Maybe (VM RealWorld)) +loadVM :: App m => ByteString -> m (Maybe (VM RealWorld)) loadVM x = do vm <- liftIO $ stToIO $ vmForEthrunCreation x vm1 <- Stepper.interpret (Fetch.zero 0 Nothing) vm Stepper.runFully @@ -3245,17 +3233,12 @@ defaultDataLocation t = then "memory" else "" -runFunction - :: App m - => Text -> ByteString -> m (Maybe ByteString) +runFunction :: App m => Text -> ByteString -> m (Maybe ByteString) runFunction c input = do x <- liftIO $ singleContract "X" c runSimpleVM (fromJust x) input -runStatements - :: App m - => Text -> [AbiValue] -> AbiType - -> m (Maybe ByteString) +runStatements :: App m => Text -> [AbiValue] -> AbiType -> m (Maybe ByteString) runStatements stmts args t = do let params = T.intercalate ", " @@ -3830,9 +3813,7 @@ data Invocation = SolidityCall Text [AbiValue] deriving Show -assertSolidityComputation - :: App m - => Invocation -> AbiValue -> m () +assertSolidityComputation :: App m => Invocation -> AbiValue -> m () assertSolidityComputation (SolidityCall s args) x = do y <- runStatements s args (abiValueType x) liftIO $ assertEqual (T.unpack s) @@ -3853,23 +3834,17 @@ checkBadCheatCode sig _ = \case (Failure _ _ (BadCheatCode s)) -> (ConcreteBuf $ into s.unFunctionSelector) ./= (ConcreteBuf $ selector sig) _ -> PBool True -allBranchesFail - :: App m - => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) +allBranchesFail :: App m => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) allBranchesFail = checkPost (Just p) where p _ = \case Success _ _ _ _ -> PBool False _ -> PBool True -reachableUserAsserts - :: App m - => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) +reachableUserAsserts :: App m => ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) reachableUserAsserts = checkPost (Just $ checkAssertions [0x01]) -checkPost - :: App m - => Maybe (Postcondition RealWorld) -> ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) +checkPost :: App m => Maybe (Postcondition RealWorld) -> ByteString -> Maybe Sig -> m (Either [SMTCex] (Expr End)) checkPost post c sig = do (e, res) <- withSolvers Z3 1 Nothing $ \s -> verifyContract s c sig [] defaultVeriOpts Nothing post From 65b44a9c0d1c49838219556af70d1c0038c3e61a Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Tue, 24 Oct 2023 17:27:41 +0200 Subject: [PATCH 20/28] Fixing up merge --- src/EVM/SymExec.hs | 49 +++++++++++++++++++++------------------------- test/test.hs | 2 +- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/EVM/SymExec.hs b/src/EVM/SymExec.hs index c5c51fb12..906ed1cde 100644 --- a/src/EVM/SymExec.hs +++ b/src/EVM/SymExec.hs @@ -563,33 +563,28 @@ verify solvers opts preState maybepost = do T.putStrLn "" T.putStrLn . T.unlines . fmap (indent 2 . ("- " <>)) . fmap formatPartial . getPartials $ flattened - case maybepost of - Nothing -> pure (expr, [Qed ()]) - Just post -> liftIO $ do - let - -- Filter out any leaves that can be statically shown to be safe - canViolate = flip filter flattened $ - \leaf -> case Expr.simplifyProp (post preState leaf) of - PBool True -> False - _ -> True - assumes = preState.constraints - withQueries = canViolate <&> \leaf -> - (assertProps opts.abstRefineConfig (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf) - putStrLn $ "Checking for reachability of " - <> show (length withQueries) - <> " potential property violation(s)" - - when opts.debug $ forM_ (zip [(1 :: Int)..] withQueries) $ \(idx, (q, leaf)) -> do - TL.writeFile - ("query-" <> show idx <> ".smt2") - ("; " <> (TL.pack $ show leaf) <> "\n\n" <> formatSMT2 q <> "\n\n(check-sat)") - - -- Dispatch the remaining branches to the solver to check for violations - results <- flip mapConcurrently withQueries $ \(query, leaf) -> do - res <- checkSat solvers query - pure (res, leaf) - let cexs = filter (\(res, _) -> not . isUnsat $ res) results - pure $ if Prelude.null cexs then (expr, [Qed ()]) else (expr, fmap toVRes cexs) + case maybepost of + Nothing -> pure (expr, [Qed ()]) + Just post -> liftIO $ do + let + -- Filter out any leaves that can be statically shown to be safe + canViolate = flip filter flattened $ + \leaf -> case Expr.simplifyProp (post preState leaf) of + PBool True -> False + _ -> True + assumes = preState.constraints + withQueries = canViolate <&> \leaf -> + (assertProps conf (PNeg (post preState leaf) : assumes <> extractProps leaf), leaf) + putStrLn $ "Checking for reachability of " + <> show (length withQueries) + <> " potential property violation(s)" + + -- Dispatch the remaining branches to the solver to check for violations + results <- flip mapConcurrently withQueries $ \(query, leaf) -> do + res <- checkSat solvers query + pure (res, leaf) + let cexs = filter (\(res, _) -> not . isUnsat $ res) results + pure $ if Prelude.null cexs then (expr, [Qed ()]) else (expr, fmap toVRes cexs) where toVRes :: (CheckSatResult, Expr End) -> VerifyResult toVRes (res, leaf) = case res of diff --git a/test/test.hs b/test/test.hs index 07e8efa4d..818979a4c 100644 --- a/test/test.hs +++ b/test/test.hs @@ -437,7 +437,7 @@ tests = testGroup "hevm" case simplified of [] -> checkEquivProp (PBool True) p [val@(PBool _)] -> checkEquivProp val p - _ -> assertFailure "must evaluate down to a literal bool" + _ -> liftIO $ assertFailure "must evaluate down to a literal bool" , testProperty "simplifyProp-equivalence-sym" $ \(p) -> ioProperty $ do let simplified = Expr.simplifyProp p checkEquivProp simplified p From 2f0b85edabbc90034d3ab6d65c4f13192006dd1b Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Tue, 24 Oct 2023 17:30:32 +0200 Subject: [PATCH 21/28] Fixing up after the merge --- test/test.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test.hs b/test/test.hs index 818979a4c..a21f68d37 100644 --- a/test/test.hs +++ b/test/test.hs @@ -320,7 +320,7 @@ tests = testGroup "hevm" let a = BufLength (ConcreteBuf "ab") simp = Expr.simplify a - assertEqual "Must be simplified down to a Lit" simp (Lit 2) + assertEqualM "Must be simplified down to a Lit" simp (Lit 2) , test "CopySlice-overflow" $ do let e = ReadWord (Lit 0x0) (CopySlice (Lit 0x0) (Lit 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc) (Lit 0x6) (ConcreteBuf "\255\255\255\255\255\255") (ConcreteBuf "")) b <- checkEquiv e (Expr.simplify e) @@ -432,13 +432,13 @@ tests = testGroup "hevm" Just asList -> do let asBuf = Expr.fromList asList checkEquiv asBuf input - , testProperty "simplifyProp-equivalence-lit" $ \(LitProp p) -> ioProperty $ do + , testProperty "simplifyProp-equivalence-lit" $ \(LitProp p) -> prop $ do let simplified = Expr.simplifyProps [p] case simplified of [] -> checkEquivProp (PBool True) p [val@(PBool _)] -> checkEquivProp val p _ -> liftIO $ assertFailure "must evaluate down to a literal bool" - , testProperty "simplifyProp-equivalence-sym" $ \(p) -> ioProperty $ do + , testProperty "simplifyProp-equivalence-sym" $ \(p) -> prop $ do let simplified = Expr.simplifyProp p checkEquivProp simplified p , testProperty "simpProp-equivalence-sym" $ \(ps :: [Prop]) -> prop $ do From 63ad6c4bb9ddaee5ca7e2524b3771eb5933cb14e Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Tue, 24 Oct 2023 17:44:19 +0200 Subject: [PATCH 22/28] Improving naming --- test/EVM/Test/BlockchainTests.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/EVM/Test/BlockchainTests.hs b/test/EVM/Test/BlockchainTests.hs index 1e06d5e10..0b4971a96 100644 --- a/test/EVM/Test/BlockchainTests.hs +++ b/test/EVM/Test/BlockchainTests.hs @@ -95,10 +95,10 @@ testsFromFile file problematicTests = do case parsed of Left "No cases to check." -> pure [] -- error "no-cases ok" Left _err -> pure [] -- error _err - Right allTests -> mapM stuff (Map.toList allTests) + Right allTests -> mapM runTest (Map.toList allTests) where - stuff :: (String , Case) -> m TestTree - stuff (name, x) = do + runTest :: (String , Case) -> m TestTree + runTest (name, x) = do exec <- toIO $ runVMTest True (name, x) pure $ testCase' name exec testCase' :: String -> Assertion -> TestTree From 1b57a206b32904a5b15b0da178766a8aba1bb6dc Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Tue, 24 Oct 2023 17:45:21 +0200 Subject: [PATCH 23/28] Removing noise --- test/rpc.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/rpc.hs b/test/rpc.hs index c4b9644cd..1d8d4fb2f 100644 --- a/test/rpc.hs +++ b/test/rpc.hs @@ -71,7 +71,6 @@ tests = testGroup "rpc" -- execute against remote state from a ds-test harness [ test "dapp-test" $ do let testFile = "test/contracts/pass/rpc.sol" - -- runSolidityTestCustom testFile ".*" Nothing Nothing False testRpcInfo Foundry >>= assertEqual "test result" True res <- runSolidityTestCustom testFile ".*" Nothing Nothing False testRpcInfo Foundry liftIO $ assertEqual "test result" True res From 8a3e60fb79508741ec72bfc8b3d91de859d327a8 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Wed, 25 Oct 2023 12:16:33 +0200 Subject: [PATCH 24/28] Allowing verbosity control through cli --- cli/cli.hs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cli/cli.hs b/cli/cli.hs index a93776108..365852252 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -86,6 +86,7 @@ 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" + , verbose :: w ::: Maybe Bool "More verbose printing of internal behaviour" , 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)" @@ -104,6 +105,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" + , verbose :: w ::: Maybe Bool "More verbose 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" @@ -140,11 +143,12 @@ data Command w , projectType :: w ::: Maybe ProjectType "Is this a Foundry or DappTools project (default: Foundry)" , rpc :: w ::: Maybe URL "Fetch state from a remote node" , number :: w ::: Maybe W256 "Block: number" - , verbose :: w ::: Maybe Int "Append call trace: {1} failures {2} all" , coverage :: w ::: Bool "Coverage analysis" , 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" + , verbose :: w ::: Maybe Bool "More verbose 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" @@ -189,6 +193,7 @@ main = do cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator" let env = Env { config = defaultConfig { dumpQueries = cmd.smtdebug + , verbose = fromMaybe False cmd.verbose , abstRefineMem = cmd.abstractMemory , abstRefineArith = cmd.abstractArithmetic , dumpTrace = cmd.trace From 016f3ec187170c53720a637876eb5394ad42fcea Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Wed, 25 Oct 2023 14:52:37 +0200 Subject: [PATCH 25/28] Fixing verbosity --- cli/cli.hs | 10 +++++----- src/EVM/Effects.hs | 4 ++-- test/test.hs | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/cli.hs b/cli/cli.hs index 365852252..eaa4f5490 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -86,7 +86,7 @@ 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" - , verbose :: w ::: Maybe Bool "More verbose printing of internal behaviour" + , verbose :: w ::: Maybe Int "Verbose printing of internal behaviour (default:0)" , 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)" @@ -105,7 +105,7 @@ 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" - , verbose :: w ::: Maybe Bool "More verbose printing of internal behaviour" + , verbose :: w ::: Maybe Int "Verbose printing of internal behaviour (default:0)" , 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" @@ -147,7 +147,7 @@ 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" - , verbose :: w ::: Maybe Bool "More verbose printing of internal behaviour" + , verbose :: w ::: Maybe Int "Verbose printing of internal behaviour (default:0)" , 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)" @@ -193,7 +193,7 @@ main = do cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator" let env = Env { config = defaultConfig { dumpQueries = cmd.smtdebug - , verbose = fromMaybe False cmd.verbose + , verbose = fromMaybe 0 cmd.verbose , abstRefineMem = cmd.abstractMemory , abstRefineArith = cmd.abstractArithmetic , dumpTrace = cmd.trace @@ -595,7 +595,7 @@ unitTestOptions cmd solvers buildOutput = do , askSmtIters = cmd.askSmtIterations , smtTimeout = cmd.smttimeout , solver = cmd.solver - , verbose = cmd.verbose + , verbose = fromMaybe 0 cmd.verbose , match = T.pack $ fromMaybe ".*" cmd.match , testParams = params , dapp = srcInfo diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs index 09a31e46d..3256b9ce8 100644 --- a/src/EVM/Effects.hs +++ b/src/EVM/Effects.hs @@ -46,7 +46,7 @@ data Config = Config { dumpQueries :: Bool , dumpExprs :: Bool , dumpEndStates :: Bool - , verbose :: Bool + , verbose :: Int , abstRefineArith :: Bool , abstRefineMem :: Bool , dumpTrace :: Bool @@ -58,7 +58,7 @@ defaultConfig = Config { dumpQueries = False , dumpExprs = False , dumpEndStates = False - , verbose = False + , verbose = 0 , abstRefineArith = False , abstRefineMem = False , dumpTrace = False diff --git a/test/test.hs b/test/test.hs index a21f68d37..11387fb40 100644 --- a/test/test.hs +++ b/test/test.hs @@ -73,7 +73,7 @@ testEnv = Env { config = defaultConfig { dumpQueries = False , dumpExprs = False , dumpEndStates = False - , verbose = False + , verbose = 0 , abstRefineArith = False , abstRefineMem = False , dumpTrace = False @@ -3122,7 +3122,7 @@ tests = testGroup "hevm" start <- liftIO $ getCurrentTime putStrLnM $ "Checking file: " <> f conf <- readConfig - when conf.verbose $ liftIO $ do + when (conf.verbose > 0) $ liftIO $ do putStrLnM "-------------Original Below-----------------" mapM_ putStrLn unfiltered putStrLnM "------------- Filtered A + Symb below-----------------" From 779675bafc322616f0bd404855af15929c052278 Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Wed, 25 Oct 2023 15:46:35 +0200 Subject: [PATCH 26/28] Fixing build --- cli/cli.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cli.hs b/cli/cli.hs index eaa4f5490..c4fd4baa8 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -595,7 +595,7 @@ unitTestOptions cmd solvers buildOutput = do , askSmtIters = cmd.askSmtIterations , smtTimeout = cmd.smttimeout , solver = cmd.solver - , verbose = fromMaybe 0 cmd.verbose + , verbose = cmd.verbose , match = T.pack $ fromMaybe ".*" cmd.match , testParams = params , dapp = srcInfo From 6e8fdca2f72e74d8e65fde40adbb83bc634a9e1a Mon Sep 17 00:00:00 2001 From: "Mate Soos @ Ethereum Foundation" <99662964+msooseth@users.noreply.github.com> Date: Wed, 25 Oct 2023 18:20:48 +0200 Subject: [PATCH 27/28] Update src/EVM/Fetch.hs Co-authored-by: dxo <6689924+d-xo@users.noreply.github.com> --- src/EVM/Fetch.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EVM/Fetch.hs b/src/EVM/Fetch.hs index 08bfe660e..a6a5c0887 100644 --- a/src/EVM/Fetch.hs +++ b/src/EVM/Fetch.hs @@ -232,7 +232,7 @@ oracle solvers info q = do Nothing -> internalError $ "oracle error: " ++ show q -type Fetcher m s = (ReadConfig m, MonadUnliftIO m) => Query s -> m (EVM s ()) +type Fetcher m s = App m => Query s -> m (EVM s ()) -- | Checks which branches are satisfiable, checking the pathconditions for consistency -- if the third argument is true. From 55c11706841a8459f6c5f711965ee63dc0f415bb Mon Sep 17 00:00:00 2001 From: Mate Soos Date: Thu, 26 Oct 2023 12:12:01 +0200 Subject: [PATCH 28/28] Not reusing the `--verbose` flag --- cli/cli.hs | 11 +++++++---- src/EVM/Effects.hs | 4 ++-- src/EVM/Fetch.hs | 1 - test/test.hs | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cli/cli.hs b/cli/cli.hs index c4fd4baa8..56e31e58f 100644 --- a/cli/cli.hs +++ b/cli/cli.hs @@ -86,7 +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" - , verbose :: w ::: Maybe Int "Verbose printing of internal behaviour (default:0)" + , 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)" @@ -105,7 +106,7 @@ 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" - , verbose :: w ::: Maybe Int "Verbose printing of internal behaviour (default:0)" + , 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" @@ -132,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" @@ -143,11 +145,12 @@ data Command w , projectType :: w ::: Maybe ProjectType "Is this a Foundry or DappTools project (default: Foundry)" , rpc :: w ::: Maybe URL "Fetch state from a remote node" , number :: w ::: Maybe W256 "Block: number" + , verbose :: w ::: Maybe Int "Append call trace: {1} failures {2} all" , coverage :: w ::: Bool "Coverage analysis" , 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" - , verbose :: w ::: Maybe Int "Verbose printing of internal behaviour (default:0)" + , 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)" @@ -193,7 +196,7 @@ main = do cmd <- Options.unwrapRecord "hevm -- Ethereum evaluator" let env = Env { config = defaultConfig { dumpQueries = cmd.smtdebug - , verbose = fromMaybe 0 cmd.verbose + , debug = cmd.debug , abstRefineMem = cmd.abstractMemory , abstRefineArith = cmd.abstractArithmetic , dumpTrace = cmd.trace diff --git a/src/EVM/Effects.hs b/src/EVM/Effects.hs index 3256b9ce8..2428f8700 100644 --- a/src/EVM/Effects.hs +++ b/src/EVM/Effects.hs @@ -46,7 +46,7 @@ data Config = Config { dumpQueries :: Bool , dumpExprs :: Bool , dumpEndStates :: Bool - , verbose :: Int + , debug :: Bool , abstRefineArith :: Bool , abstRefineMem :: Bool , dumpTrace :: Bool @@ -58,7 +58,7 @@ defaultConfig = Config { dumpQueries = False , dumpExprs = False , dumpEndStates = False - , verbose = 0 + , debug = False , abstRefineArith = False , abstRefineMem = False , dumpTrace = False diff --git a/src/EVM/Fetch.hs b/src/EVM/Fetch.hs index a6a5c0887..a3acc2ff7 100644 --- a/src/EVM/Fetch.hs +++ b/src/EVM/Fetch.hs @@ -29,7 +29,6 @@ import Numeric.Natural (Natural) import System.Process import Control.Monad.IO.Class import EVM.Effects -import Control.Monad.IO.Unlift -- | Abstract representation of an RPC fetch request data RpcQuery a where diff --git a/test/test.hs b/test/test.hs index 11387fb40..826b6f766 100644 --- a/test/test.hs +++ b/test/test.hs @@ -73,7 +73,7 @@ testEnv = Env { config = defaultConfig { dumpQueries = False , dumpExprs = False , dumpEndStates = False - , verbose = 0 + , debug = False , abstRefineArith = False , abstRefineMem = False , dumpTrace = False @@ -3122,7 +3122,7 @@ tests = testGroup "hevm" start <- liftIO $ getCurrentTime putStrLnM $ "Checking file: " <> f conf <- readConfig - when (conf.verbose > 0) $ liftIO $ do + when conf.debug $ liftIO $ do putStrLnM "-------------Original Below-----------------" mapM_ putStrLn unfiltered putStrLnM "------------- Filtered A + Symb below-----------------"