diff --git a/lib/Echidna.hs b/lib/Echidna.hs index 4c79102c7..6d915c693 100644 --- a/lib/Echidna.hs +++ b/lib/Echidna.hs @@ -48,12 +48,15 @@ prepareContract -> IO (VM, World, GenDict) prepareContract env contracts solFiles specifiedContract seed = do let solConf = env.cfg.solConf + let campaignConf = env.cfg.campaignConf -- compile and load contracts (vm, funs, testNames, signatureMap) <- loadSpecified env specifiedContract contracts -- run processors - slitherInfo <- runSlither (NE.head solFiles) solConf + mainSlitherInfo <- runSlither (NE.head solFiles) solConf + complementarySlitherInfo <- loadSlitherInfos campaignConf + let slitherInfo = appendSlitherInfo mainSlitherInfo complementarySlitherInfo case find (< minSupportedSolcVersion) slitherInfo.solcVersions of Just version | detectVyperVersion version -> pure () Just version -> throwM $ OutdatedSolcVersion version diff --git a/lib/Echidna/Config.hs b/lib/Echidna/Config.hs index 575b1a1b4..7b25fa0f9 100644 --- a/lib/Echidna/Config.hs +++ b/lib/Echidna/Config.hs @@ -93,6 +93,7 @@ instance FromJSON EConfigWithUsage where <*> v ..:? "seed" <*> v ..:? "dictFreq" ..!= 0.40 <*> v ..:? "corpusDir" ..!= Nothing + <*> v ..:? "slitherInfoDir" ..!= Nothing <*> v ..:? "mutConsts" ..!= defaultMutationConsts <*> v ..:? "coverageFormats" ..!= [Txt,Html,Lcov] <*> v ..:? "workers" diff --git a/lib/Echidna/Processor.hs b/lib/Echidna/Processor.hs index 9bd48107a..576967ef2 100644 --- a/lib/Echidna/Processor.hs +++ b/lib/Echidna/Processor.hs @@ -10,7 +10,7 @@ import Data.ByteString.Base16 qualified as BS16 (decode) import Data.ByteString.Lazy.Char8 qualified as BSL import Data.ByteString.UTF8 qualified as BSU import Data.Either (fromRight) -import Data.List (isPrefixOf) +import Data.List (isPrefixOf, isSuffixOf) import Data.List.NonEmpty qualified as NE import Data.Map (Map) import Data.Map qualified as Map @@ -19,7 +19,7 @@ import Data.SemVer (Version, fromText) import Data.Set (Set) import Data.Set qualified as Set import Data.Text (pack) -import System.Directory (findExecutable) +import System.Directory (findExecutable, listDirectory) import System.Exit (ExitCode(..)) import System.Process (StdStream(..), readCreateProcessWithExitCode, proc, std_err) import Text.Read (readMaybe) @@ -30,6 +30,7 @@ import EVM.Types (Addr(..), FunctionSelector) import Echidna.ABI (hashSig, makeNumAbiValues, makeArrayAbiValues) import Echidna.Types.Signature (ContractName, FunctionName) import Echidna.Types.Solidity (SolConf(..)) +import Echidna.Types.Campaign (CampaignConf(..)) import Echidna.Utility (measureIO) -- | Things that can go wrong trying to run a processor. Read the 'Show' @@ -135,12 +136,39 @@ runSlither fp solConf = do (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) + ExitSuccess -> decodeSlitherPrinterJSON (BSL.pack out) ExitFailure _ -> throwM $ ProcessorFailure "slither" err +decodeSlitherPrinterJSON :: (MonadThrow f) => BSL.ByteString -> f SlitherInfo +decodeSlitherPrinterJSON buffer = + case eitherDecode buffer of + Right si -> pure si + Left msg -> throwM $ ProcessorFailure "slither" ("decoding slither output failed:\n" ++ msg) + +loadSlitherInfos :: CampaignConf -> IO SlitherInfo +loadSlitherInfos solConf = case solConf.slitherInfoDir of + Nothing -> return noInfo + Just d -> decodeSlitherPrinterJSONBatch d + +decodeSlitherPrinterJSONBatch :: FilePath -> IO SlitherInfo +decodeSlitherPrinterJSONBatch d = do + fs <- filter (".json" `Data.List.isSuffixOf`) <$> listDirectory d + bbs <- mapM BSL.readFile fs + sis <- mapM decodeSlitherPrinterJSON bbs + return $ foldr appendSlitherInfo noInfo sis + +appendSlitherInfo :: SlitherInfo -> SlitherInfo -> SlitherInfo +appendSlitherInfo si1 si2 = + SlitherInfo { + payableFunctions = Map.union si1.payableFunctions si2.payableFunctions, + constantFunctions = Map.union si1.constantFunctions si2.constantFunctions, + asserts = Map.union si1.asserts si2.asserts, + constantValues = Map.union si1.constantValues si2.constantValues, + generationGraph = Map.union si1.generationGraph si2.generationGraph, + solcVersions = si1.solcVersions ++ si2.solcVersions, + fallbackDefined = si1.fallbackDefined ++ si2.fallbackDefined, + receiveDefined = si1.receiveDefined ++ si2.receiveDefined + } + noInfo :: SlitherInfo noInfo = SlitherInfo mempty mempty mempty mempty mempty [] [] [] diff --git a/lib/Echidna/Types/Campaign.hs b/lib/Echidna/Types/Campaign.hs index 85f0ca478..345096a7b 100644 --- a/lib/Echidna/Types/Campaign.hs +++ b/lib/Echidna/Types/Campaign.hs @@ -34,6 +34,8 @@ data CampaignConf = CampaignConf -- ^ Frequency for the use of dictionary values in the random transactions , corpusDir :: Maybe FilePath -- ^ Directory to load and save lists of transactions + , slitherInfoDir :: Maybe FilePath + -- ^ Directory to load additional slither info files , mutConsts :: MutationConsts Integer -- ^ Directory to load and save lists of transactions , coverageFormats :: [CoverageFileType]