Skip to content

Commit

Permalink
Make slither optional (#1159)
Browse files Browse the repository at this point in the history
  • Loading branch information
arcz authored Jan 8, 2024
1 parent 937102b commit 9d502be
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 54 deletions.
2 changes: 1 addition & 1 deletion lib/Echidna.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 24 additions & 8 deletions lib/Echidna/Solidity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
80 changes: 35 additions & 45 deletions lib/Echidna/Processor.hs → lib/Echidna/SourceAnalysis/Slither.hs
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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 =
Expand Down Expand Up @@ -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 [] [] []

0 comments on commit 9d502be

Please sign in to comment.