Skip to content
Open
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
4 changes: 4 additions & 0 deletions lib/Echidna/Utility.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Control.Monad (unless)
import Control.Monad.Catch (bracket)
import Data.Time (diffUTCTime, getCurrentTime, zonedTimeToLocalTime, LocalTime, getZonedTime)
import Data.Time.Format (defaultTimeLocale, formatTime)
import Language.Haskell.TH
import System.Directory (getDirectoryContents, getCurrentDirectory, setCurrentDirectory)
import System.IO (hFlush, stdout)

Expand Down Expand Up @@ -38,3 +39,6 @@ withCurrentDirectory dir action =
bracket getCurrentDirectory setCurrentDirectory $ \_ -> do
setCurrentDirectory dir
action

includeFile :: FilePath -> Q Exp
includeFile fp = LitE . StringL <$> runIO (readFile fp)
12 changes: 12 additions & 0 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ dependencies:
- MonadRandom
- mtl
- text
- transformers
- time
- unliftio
- utf8-string
- vector
- with-utf8
- word-wrap
- yaml
- http-conduit
- html-conduit
- xml-conduit
- template-haskell

language: GHC2021

Expand Down
48 changes: 37 additions & 11 deletions src/Main.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}

module Main where

Expand All @@ -22,7 +23,7 @@ import Data.Word (Word8, Word16, Word64)
import Main.Utf8 (withUtf8)
import Options.Applicative
import Paths_echidna (version)
import System.Directory (createDirectoryIfMissing)
import System.Directory (createDirectoryIfMissing, doesFileExist)
import System.Environment (lookupEnv)
import System.Exit (exitWith, exitSuccess, ExitCode(..))
import System.FilePath ((</>), (<.>))
Expand All @@ -47,11 +48,26 @@ import Echidna.Types.Solidity
import Echidna.Types.Test (TestMode, EchidnaTest(..))
import Echidna.UI
import Echidna.UI.Report (ppFailWithTraces, ppTestName)
import Echidna.Utility (measureIO)
import Echidna.Utility (includeFile, measureIO)

main :: IO ()
main = withUtf8 $ withCP65001 $ do
opts@Options{..} <- execParser optsParser
cli <- execParser cliParser
case cli of
InitCommand -> do
let config = "echidna.yaml"
configExists <- doesFileExist config
if configExists then do
putStrLn $ "Config file " <> config <> " already exists."
exitWith (ExitFailure 1)
else do
writeFile config $(includeFile "tests/solidity/basic/default.yaml")
putStrLn $ "Sample config file written to " <> config
FuzzCommand fuzzOpts ->
fuzz fuzzOpts

fuzz :: FuzzOptions -> IO ()
fuzz opts@FuzzOptions{..} = do
EConfigWithUsage loadedCfg ks _ <-
maybe (pure (EConfigWithUsage defaultConfig mempty mempty)) parseConfig cliConfigFilepath
cfg <- overrideConfig loadedCfg opts
Expand Down Expand Up @@ -118,7 +134,11 @@ main = withUtf8 $ withCP65001 $ do

if isSuccessful tests then exitSuccess else exitWith (ExitFailure 1)

data Options = Options
data CLI
= InitCommand
| FuzzCommand FuzzOptions

data FuzzOptions = FuzzOptions
{ cliFilePath :: NE.NonEmpty FilePath
, cliWorkers :: Maybe Word8
, cliServerPort :: Maybe Word16
Expand Down Expand Up @@ -147,19 +167,25 @@ data Options = Options
, cliSymExecNSolvers :: Maybe Int
}

optsParser :: ParserInfo Options
optsParser = info (helper <*> versionOption <*> options) $ fullDesc
cliParser :: ParserInfo CLI
cliParser = info (helper <*> versionOption <*> commands) $ fullDesc
<> progDesc "EVM property-based testing framework"
<> header "Echidna"
where
commands = subparser $
command "init" (info (pure InitCommand)
(progDesc "Write a sample config file to echidna.yaml"))
<> command "fuzz" (info (FuzzCommand <$> fuzzOptions)
(progDesc "Run fuzzing"))

bool :: ReadM Bool
bool = maybeReader (f . map toLower) where
f "true" = Just True
f "false" = Just False
f _ = Nothing

options :: Parser Options
options = Options . NE.fromList
fuzzOptions :: Parser FuzzOptions
fuzzOptions = FuzzOptions . NE.fromList
<$> some (argument str (metavar "FILES"
<> help "Solidity files to analyze"))
<*> optional (option auto $ long "workers"
Expand Down Expand Up @@ -240,8 +266,8 @@ versionOption = infoOption
("Echidna " ++ showVersion version)
(long "version" <> help "Show version")

overrideConfig :: EConfig -> Options -> IO EConfig
overrideConfig config Options{..} = do
overrideConfig :: EConfig -> FuzzOptions -> IO EConfig
overrideConfig config FuzzOptions{..} = do
envRpcUrl <- Onchain.rpcUrlEnv
envRpcBlock <- Onchain.rpcBlockEnv
envEtherscanApiKey <- Onchain.etherscanApiKey
Expand Down Expand Up @@ -295,4 +321,4 @@ overrideConfig config Options{..} = do
printProjectName :: Maybe Text -> IO ()
printProjectName (Just name) = putStrLn $
"This is Echidna " <> showVersion version <> " running on project `" <> T.unpack name <> "`"
printProjectName Nothing = pure ()
printProjectName Nothing = pure ()
Loading