Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Make slither optional #1159

Merged
merged 1 commit into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 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
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 [] [] []