From 9d502be26597e26c468f94f7c56a6c5692cd0577 Mon Sep 17 00:00:00 2001 From: Artur Cygan Date: Tue, 9 Jan 2024 00:54:28 +0100 Subject: [PATCH] Make slither optional (#1159) --- lib/Echidna.hs | 2 +- lib/Echidna/Solidity.hs | 32 ++++++-- .../Slither.hs} | 80 ++++++++----------- 3 files changed, 60 insertions(+), 54 deletions(-) rename lib/Echidna/{Processor.hs => SourceAnalysis/Slither.hs} (69%) diff --git a/lib/Echidna.hs b/lib/Echidna.hs index f36f39b1f..ea207e424 100644 --- a/lib/Echidna.hs +++ b/lib/Echidna.hs @@ -18,7 +18,7 @@ import EVM.Types hiding (Env) import Echidna.ABI import Echidna.Etheno (loadEtheno, extractFromEtheno) import Echidna.Output.Corpus -import Echidna.Processor +import Echidna.SourceAnalysis.Slither import Echidna.Solidity import Echidna.Symbolic (forceAddr) import Echidna.Test (createTests) diff --git a/lib/Echidna/Solidity.hs b/lib/Echidna/Solidity.hs index 5f3f75f51..1300730e3 100644 --- a/lib/Echidna/Solidity.hs +++ b/lib/Echidna/Solidity.hs @@ -12,6 +12,7 @@ import Data.List (find, partition, isSuffixOf, (\\)) import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty qualified as NE import Data.List.NonEmpty.Extra qualified as NEE +import Data.Map (Map) import Data.Map qualified as Map import Data.Maybe (isJust, isNothing, catMaybes, listToMaybe, mapMaybe) import Data.Set (Set) @@ -38,12 +39,12 @@ import Echidna.Deploy (deployContracts, deployBytecodes) import Echidna.Etheno (loadEthenoBatch) import Echidna.Events (EventMap, extractEvents) import Echidna.Exec (execTx, initialVM) -import Echidna.Processor +import Echidna.SourceAnalysis.Slither import Echidna.Symbolic (forceAddr) import Echidna.Test (createTests, isAssertionMode, isPropertyMode, isDapptestMode) import Echidna.Types.Config (EConfig(..), Env(..)) import Echidna.Types.Signature - (ContractName, SolSignature, SignatureMap, getBytecodeMetadata) + (ContractName, SolSignature, SignatureMap, getBytecodeMetadata, FunctionName) import Echidna.Types.Solidity import Echidna.Types.Test (EchidnaTest(..)) import Echidna.Types.Tx @@ -316,13 +317,28 @@ mkWorld -> Maybe ContractName -> SlitherInfo -> World -mkWorld SolConf{sender, testMode} em m c si = +mkWorld SolConf{sender, testMode} eventMap sigMap maybeContract slitherInfo = let - ps = filterResults c si.payableFunctions - as = if isAssertionMode testMode then filterResults c si.asserts else [] - cs = if isDapptestMode testMode then [] else filterResults c si.constantFunctions \\ as - (hm, lm) = prepareHashMaps cs as $ filterFallbacks c si.fallbackDefined si.receiveDefined m - in World sender hm lm ps em + payableSigs = filterResults maybeContract slitherInfo.payableFunctions + as = if isAssertionMode testMode then filterResults maybeContract slitherInfo.asserts else [] + cs = if isDapptestMode testMode then [] else filterResults maybeContract slitherInfo.constantFunctions \\ as + (highSignatureMap, lowSignatureMap) = prepareHashMaps cs as $ + filterFallbacks maybeContract slitherInfo.fallbackDefined slitherInfo.receiveDefined sigMap + in World { senders = sender + , highSignatureMap + , lowSignatureMap + , payableSigs + , eventMap + } + +-- | This function is used to filter the lists of function names according to the supplied +-- contract name (if any) and returns a list of hashes +filterResults :: Maybe ContractName -> Map ContractName [FunctionName] -> [FunctionSelector] +filterResults (Just contractName) rs = + case Map.lookup contractName rs of + Nothing -> filterResults Nothing rs + Just sig -> hashSig <$> sig +filterResults Nothing rs = hashSig <$> (concat . Map.elems) rs filterFallbacks :: Maybe ContractName diff --git a/lib/Echidna/Processor.hs b/lib/Echidna/SourceAnalysis/Slither.hs similarity index 69% rename from lib/Echidna/Processor.hs rename to lib/Echidna/SourceAnalysis/Slither.hs index 9bd48107a..cb60028bb 100644 --- a/lib/Echidna/Processor.hs +++ b/lib/Echidna/SourceAnalysis/Slither.hs @@ -1,9 +1,7 @@ {-# LANGUAGE RecordWildCards #-} -module Echidna.Processor where +module Echidna.SourceAnalysis.Slither where -import Control.Exception (Exception) -import Control.Monad.Catch (MonadThrow(..)) import Data.Aeson ((.:), (.:?), (.!=), eitherDecode, parseJSON, withEmbeddedJSON, withObject) import Data.Aeson.Types (FromJSON, Parser, Value(String)) import Data.ByteString.Base16 qualified as BS16 (decode) @@ -25,33 +23,13 @@ import System.Process (StdStream(..), readCreateProcessWithExitCode, proc, std_e import Text.Read (readMaybe) import EVM.ABI (AbiValue(..)) -import EVM.Types (Addr(..), FunctionSelector) +import EVM.Types (Addr(..)) -import Echidna.ABI (hashSig, makeNumAbiValues, makeArrayAbiValues) +import Echidna.ABI (makeNumAbiValues, makeArrayAbiValues) import Echidna.Types.Signature (ContractName, FunctionName) import Echidna.Types.Solidity (SolConf(..)) import Echidna.Utility (measureIO) - --- | Things that can go wrong trying to run a processor. Read the 'Show' --- instance for more detailed explanations. -data ProcException = ProcessorFailure String String - | ProcessorNotFound String String - -instance Show ProcException where - show = \case - ProcessorFailure p e -> "Error running " ++ p ++ ":\n" ++ e - ProcessorNotFound p e -> "Cannot find " ++ p ++ " in PATH.\n" ++ e - -instance Exception ProcException - --- | This function is used to filter the lists of function names according to the supplied --- contract name (if any) and returns a list of hashes -filterResults :: Maybe ContractName -> Map ContractName [FunctionName] -> [FunctionSelector] -filterResults (Just c) rs = - case Map.lookup c rs of - Nothing -> filterResults Nothing rs - Just s -> hashSig <$> s -filterResults Nothing rs = hashSig <$> (concat . Map.elems) rs +import System.IO (stderr, hPutStrLn) enhanceConstants :: SlitherInfo -> Set AbiValue enhanceConstants si = @@ -125,22 +103,34 @@ instance FromJSON SlitherInfo where -- Slither processing runSlither :: FilePath -> SolConf -> IO SlitherInfo runSlither fp solConf = do - path <- findExecutable "slither" >>= \case - Nothing -> throwM $ - ProcessorNotFound "slither" "You should install it using 'pip3 install slither-analyzer --user'" - Just path -> pure path - - let args = ["--ignore-compile", "--print", "echidna", "--json", "-"] - ++ solConf.cryticArgs ++ [fp] - (ec, out, err) <- measureIO solConf.quiet ("Running slither on " <> fp) $ - readCreateProcessWithExitCode (proc path args) {std_err = Inherit} "" - case ec of - ExitSuccess -> - case eitherDecode (BSL.pack out) of - Right si -> pure si - Left msg -> throwM $ - ProcessorFailure "slither" ("decoding slither output failed:\n" ++ msg) - ExitFailure _ -> throwM $ ProcessorFailure "slither" err - -noInfo :: SlitherInfo -noInfo = SlitherInfo mempty mempty mempty mempty mempty [] [] [] + findExecutable "slither" >>= \case + Nothing -> do + hPutStrLn stderr $ + "WARNING: slither not found. Echidna uses Slither (https://github.com/crytic/slither)" + <> " to perform source analysis, which makes fuzzing more effective. You should install it with" + <> " 'pip3 install slither-analyzer --user'" + pure emptySlitherInfo + Just path -> do + let args = ["--ignore-compile", "--print", "echidna", "--json", "-"] + ++ solConf.cryticArgs ++ [fp] + (exitCode, out, err) <- measureIO solConf.quiet ("Running slither on " <> fp) $ + readCreateProcessWithExitCode (proc path args) {std_err = Inherit} "" + case exitCode of + ExitSuccess -> + case eitherDecode (BSL.pack out) of + Right si -> pure si + Left msg -> do + hPutStrLn stderr $ + "WARNING: Decoding slither output failed. Echidna will continue," + <> " however fuzzing will likely be less effective.\n" + <> msg + pure emptySlitherInfo + ExitFailure _ -> do + hPutStrLn stderr $ + "WARNING: Running slither failed. Echidna will continue," + <> " however fuzzing will likely be less effective.\n" + <> err + pure emptySlitherInfo + +emptySlitherInfo :: SlitherInfo +emptySlitherInfo = SlitherInfo mempty mempty mempty mempty mempty [] [] []