Skip to content

Commit

Permalink
Add CLI command to generate sample config
Browse files Browse the repository at this point in the history
  • Loading branch information
arcz committed Jul 22, 2023
1 parent 21e6e52 commit 87a6d60
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 73 deletions.
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)
1 change: 1 addition & 0 deletions package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies:
- http-conduit
- html-conduit
- xml-conduit
- template-haskell

language: GHC2021

Expand Down
47 changes: 36 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 Down Expand Up @@ -53,12 +54,27 @@ import Echidna.Output.Source
import Echidna.Output.Corpus
import Echidna.RPC qualified as RPC
import Echidna.Solidity (compileContracts, selectBuildOutput)
import Echidna.Utility (measureIO)
import Echidna.Utility (includeFile, measureIO)
import Etherscan qualified

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 @@ -222,7 +238,11 @@ readFileIfExists path = do
exists <- doesFileExist path
if exists then Just <$> BS.readFile path else pure Nothing

data Options = Options
data CLI
= InitCommand
| FuzzCommand FuzzOptions

data FuzzOptions = FuzzOptions
{ cliFilePath :: NE.NonEmpty FilePath
, cliWorkers :: Maybe Word8
, cliSelectedContract :: Maybe Text
Expand All @@ -243,13 +263,19 @@ data Options = Options
, cliSolcArgs :: Maybe String
}

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"

options :: Parser Options
options = Options
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"))

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 @@ -307,8 +333,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
rpcUrl <- RPC.rpcUrlEnv
rpcBlock <- RPC.rpcBlockEnv
pure $
Expand Down Expand Up @@ -350,4 +376,3 @@ overrideConfig config Options{..} = do
, testMode = maybe solConf.testMode validateTestMode cliTestMode
, allContracts = cliAllContracts || solConf.allContracts
}

149 changes: 87 additions & 62 deletions tests/solidity/basic/default.yaml
Original file line number Diff line number Diff line change
@@ -1,91 +1,116 @@
# TODO
#select the mode to test, which can be property, assertion, overflow, exploration, optimization
# Number of fuzzing workers to run, should not exceed the number of available cores.
workers: 1

# Test mode, one of: property, assertion, overflow, exploration, optimization.
testMode: "property"
#check if some contract was destructed or not

# Directory to save the corpus and coverage reports; disabled by default
corpusDir: null
# List of file formats to save coverage reports in; default is all possible formats
coverageFormats: ["txt","html","lcov"]
# If specified, disables the interactive UI and prints the results to stdout.
# Can be "text", "json" or "none".
format: null
# Produces (much) less verbose output
quiet: false

# Check if some contract was destructed or not
testDestruction: false
#psender is the sender for property transactions; by default intentionally
#the same as contract deployer

# psender is the sender for property transactions; by default intentionally
# the same as contract deployer
psender: "0x10000"
#prefix is the prefix for Boolean functions that are properties to be checked

# Prefix used to find property functions. Property functions don't take any
# arguments and return bool.
prefix: "echidna_"
#propMaxGas defines gas cost at which a property fails
propMaxGas: 8000030
#testMaxGas is a gas limit; does not cause failure, but terminates sequence
testMaxGas: 8000030
#maxGasprice is the maximum gas price
maxGasprice: 0
#testLimit is the number of test sequences to run
testLimit: 50000
#stopOnFail makes echidna terminate as soon as any property fails and has been shrunk
stopOnFail: false
#estimateGas makes echidna perform analysis of maximum gas costs for functions (experimental)
estimateGas: false
#seqLen defines how many transactions are in a test sequence

# The number of transactions generated in a test sequence.
seqLen: 100
#shrinkLimit determines how much effort is spent shrinking failing sequences
# The number of test sequences to run.
testLimit: 50000
# How many attemts to run when shrinking the failing sequences.
shrinkLimit: 5000
#coverage controls coverage guided testing
coverage: false
#format can be "text" or "json" for different output (human or machine readable)
format: "text"
#contractAddr is the address of the contract itself

# Stop fuzzing as soon as any property fails and has been shrunk.
stopOnFail: false
# Whether coverage-guided fuzzing is enabled.
coverage: true
# Address of the contract itself
contractAddr: "0x00a329c0648769a73afac7f9381e08fb43dbea72"
#deployer is address of the contract deployer (who often is privileged owner, etc.)
# Address of the contract deployer (who often is privileged owner, etc.)
deployer: "0x30000"
#sender is set of addresses transactions may originate from
# Set of addresses transactions may originate from
sender: ["0x10000", "0x20000", "0x30000"]
#balanceAddr is default balance for addresses
# Default balance for addresses
balanceAddr: 0xffffffff
#balanceContract overrides balanceAddr for the contract address
# Overrides balanceAddr for the contract address
balanceContract: 0
#codeSize max code size for deployed contratcs (default 24576, per EIP-170)
# Max code size for deployed contratcs (default 24576, per EIP-170)
codeSize: 0x6000
#solcArgs allows special args to solc

# Pass additional CLI options to crytic-compile.
# See: https://github.com/crytic/crytic-compile
cryticArgs: []
# Pass additional CLI options to solc.
solcArgs: ""
#solcLibs is solc libraries
# solcLibs is solc libraries
solcLibs: []
#cryticArgs allows special args to crytic
cryticArgs: []
#quiet produces (much) less verbose output
quiet: false
#initialize the blockchain with some data

# Initialize the blockchain with some data
initialize: null
#initialize the blockchain with some predeployed contracts in some addresses
# Initialize the blockchain with some predeployed contracts in some addresses
deployContracts: []
#initialize the blockchain with some bytecode in some addresses
# Initialize the blockchain with some bytecode in some addresses
deployBytecodes: []
#whether ot not to fuzz all contracts
# Whether ot not to fuzz all contracts
allContracts: false
#timeout controls test timeout settings

# Set a timeout to stop fuzzing after N seconds.
timeout: null
#seed not defined by default, is the random seed
#seed: 0
#dictFreq controls how often to use echidna's internal dictionary vs random
#values

# Use to fix the seed for random number generator. If not specified, a new
# random seed value is used every time. A positive integer.
# seed: 0

# How often to use echidna's internal dictionary vs random values while fuzzing.
# Value between 0 and 1.
dictFreq: 0.40

# Defines gas cost at which a property fails
propMaxGas: 8000030
# Gas limit; does not cause failure, but terminates a sequence
testMaxGas: 8000030
# Maximum gas price
maxGasprice: 0
# Maximum value to send to payable functions
maxValue: 100000000000000000000 # 100 eth
# Maximum time between generated txs; default is one week
maxTimeDelay: 604800
#maximum time between generated txs; default is one week
# Maximum number of blocks elapsed between generated txs; default is expected
# increment in one week
maxBlockDelay: 60480
#maximum number of blocks elapsed between generated txs; default is expected increment in one week
# timeout:
#campaign timeout (in seconds)
# list of methods to filter

# List of methods to filter
filterFunctions: []
# by default, blacklist methods in filterFunctions
filterBlacklist: true
# enable or disable ffi HEVM cheatcode

# Enable the ffi HEVM cheatcode. It is disabled by default for security.
# See: https://hevm.dev/controlling-the-unit-testing-environment.html.
allowFFI: false
#directory to save the corpus; by default is disabled
corpusDir: null
# list of file formats to save coverage reports in; default is all possible formats
coverageFormats: ["txt","html","lcov"]
# constants for corpus mutations (for experimentation only)
mutConsts: [1, 1, 1, 1]
# maximum value to send to payable functions
maxValue: 100000000000000000000 # 100 eth

# Configure to perform "on-chain fuzzing".
# See: https://blog.trailofbits.com/2023/07/21/fuzzing-on-chain-contracts-with-echidna/
# URL to fetch contracts over RPC
rpcUrl: null
# block number to use when fetching over RPC
# Block number to use when fetching over RPC
rpcBlock: null
# number of workers
workers: 1

# ===
# NOTE: The experimental options below shouldn't be touched unless you know what you are doing.
# ===
# Constants for corpus mutations (for experimentation only)
mutConsts: [1, 1, 1, 1]
# Perform analysis of maximum gas costs for functions (experimental)
estimateGas: false

0 comments on commit 87a6d60

Please sign in to comment.