Skip to content

Commit 87a6d60

Browse files
committed
Add CLI command to generate sample config
1 parent 21e6e52 commit 87a6d60

File tree

4 files changed

+128
-73
lines changed

4 files changed

+128
-73
lines changed

Diff for: lib/Echidna/Utility.hs

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Control.Monad (unless)
44
import Control.Monad.Catch (bracket)
55
import Data.Time (diffUTCTime, getCurrentTime, zonedTimeToLocalTime, LocalTime, getZonedTime)
66
import Data.Time.Format (defaultTimeLocale, formatTime)
7+
import Language.Haskell.TH
78
import System.Directory (getDirectoryContents, getCurrentDirectory, setCurrentDirectory)
89
import System.IO (hFlush, stdout)
910

@@ -38,3 +39,6 @@ withCurrentDirectory dir action =
3839
bracket getCurrentDirectory setCurrentDirectory $ \_ -> do
3940
setCurrentDirectory dir
4041
action
42+
43+
includeFile :: FilePath -> Q Exp
44+
includeFile fp = LitE . StringL <$> runIO (readFile fp)

Diff for: package.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dependencies:
4949
- http-conduit
5050
- html-conduit
5151
- xml-conduit
52+
- template-haskell
5253

5354
language: GHC2021
5455

Diff for: src/Main.hs

+36-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE RecordWildCards #-}
2+
{-# LANGUAGE TemplateHaskell #-}
23

34
module Main where
45

@@ -53,12 +54,27 @@ import Echidna.Output.Source
5354
import Echidna.Output.Corpus
5455
import Echidna.RPC qualified as RPC
5556
import Echidna.Solidity (compileContracts, selectBuildOutput)
56-
import Echidna.Utility (measureIO)
57+
import Echidna.Utility (includeFile, measureIO)
5758
import Etherscan qualified
5859

5960
main :: IO ()
6061
main = withUtf8 $ withCP65001 $ do
61-
opts@Options{..} <- execParser optsParser
62+
cli <- execParser cliParser
63+
case cli of
64+
InitCommand -> do
65+
let config = "echidna.yaml"
66+
configExists <- doesFileExist config
67+
if configExists then do
68+
putStrLn $ "Config file " <> config <> " already exists."
69+
exitWith (ExitFailure 1)
70+
else do
71+
writeFile config $(includeFile "tests/solidity/basic/default.yaml")
72+
putStrLn $ "Sample config file written to " <> config
73+
FuzzCommand fuzzOpts ->
74+
fuzz fuzzOpts
75+
76+
fuzz :: FuzzOptions -> IO ()
77+
fuzz opts@FuzzOptions{..} = do
6278
EConfigWithUsage loadedCfg ks _ <-
6379
maybe (pure (EConfigWithUsage defaultConfig mempty mempty)) parseConfig cliConfigFilepath
6480
cfg <- overrideConfig loadedCfg opts
@@ -222,7 +238,11 @@ readFileIfExists path = do
222238
exists <- doesFileExist path
223239
if exists then Just <$> BS.readFile path else pure Nothing
224240

225-
data Options = Options
241+
data CLI
242+
= InitCommand
243+
| FuzzCommand FuzzOptions
244+
245+
data FuzzOptions = FuzzOptions
226246
{ cliFilePath :: NE.NonEmpty FilePath
227247
, cliWorkers :: Maybe Word8
228248
, cliSelectedContract :: Maybe Text
@@ -243,13 +263,19 @@ data Options = Options
243263
, cliSolcArgs :: Maybe String
244264
}
245265

246-
optsParser :: ParserInfo Options
247-
optsParser = info (helper <*> versionOption <*> options) $ fullDesc
266+
cliParser :: ParserInfo CLI
267+
cliParser = info (helper <*> versionOption <*> commands) $ fullDesc
248268
<> progDesc "EVM property-based testing framework"
249269
<> header "Echidna"
250-
251-
options :: Parser Options
252-
options = Options
270+
where
271+
commands = subparser $
272+
command "init" (info (pure InitCommand)
273+
(progDesc "Write a sample config file to echidna.yaml"))
274+
<> command "fuzz" (info (FuzzCommand <$> fuzzOptions)
275+
(progDesc "Run fuzzing"))
276+
277+
fuzzOptions :: Parser FuzzOptions
278+
fuzzOptions = FuzzOptions
253279
<$> (NE.fromList <$> some (argument str (metavar "FILES"
254280
<> help "Solidity files to analyze")))
255281
<*> optional (option auto $ long "workers"
@@ -307,8 +333,8 @@ versionOption = infoOption
307333
("Echidna " ++ showVersion version)
308334
(long "version" <> help "Show version")
309335

310-
overrideConfig :: EConfig -> Options -> IO EConfig
311-
overrideConfig config Options{..} = do
336+
overrideConfig :: EConfig -> FuzzOptions -> IO EConfig
337+
overrideConfig config FuzzOptions{..} = do
312338
rpcUrl <- RPC.rpcUrlEnv
313339
rpcBlock <- RPC.rpcBlockEnv
314340
pure $
@@ -350,4 +376,3 @@ overrideConfig config Options{..} = do
350376
, testMode = maybe solConf.testMode validateTestMode cliTestMode
351377
, allContracts = cliAllContracts || solConf.allContracts
352378
}
353-

Diff for: tests/solidity/basic/default.yaml

+87-62
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,116 @@
1-
# TODO
2-
#select the mode to test, which can be property, assertion, overflow, exploration, optimization
1+
# Number of fuzzing workers to run, should not exceed the number of available cores.
2+
workers: 1
3+
4+
# Test mode, one of: property, assertion, overflow, exploration, optimization.
35
testMode: "property"
4-
#check if some contract was destructed or not
6+
7+
# Directory to save the corpus and coverage reports; disabled by default
8+
corpusDir: null
9+
# List of file formats to save coverage reports in; default is all possible formats
10+
coverageFormats: ["txt","html","lcov"]
11+
# If specified, disables the interactive UI and prints the results to stdout.
12+
# Can be "text", "json" or "none".
13+
format: null
14+
# Produces (much) less verbose output
15+
quiet: false
16+
17+
# Check if some contract was destructed or not
518
testDestruction: false
6-
#psender is the sender for property transactions; by default intentionally
7-
#the same as contract deployer
19+
20+
# psender is the sender for property transactions; by default intentionally
21+
# the same as contract deployer
822
psender: "0x10000"
9-
#prefix is the prefix for Boolean functions that are properties to be checked
23+
24+
# Prefix used to find property functions. Property functions don't take any
25+
# arguments and return bool.
1026
prefix: "echidna_"
11-
#propMaxGas defines gas cost at which a property fails
12-
propMaxGas: 8000030
13-
#testMaxGas is a gas limit; does not cause failure, but terminates sequence
14-
testMaxGas: 8000030
15-
#maxGasprice is the maximum gas price
16-
maxGasprice: 0
17-
#testLimit is the number of test sequences to run
18-
testLimit: 50000
19-
#stopOnFail makes echidna terminate as soon as any property fails and has been shrunk
20-
stopOnFail: false
21-
#estimateGas makes echidna perform analysis of maximum gas costs for functions (experimental)
22-
estimateGas: false
23-
#seqLen defines how many transactions are in a test sequence
27+
28+
# The number of transactions generated in a test sequence.
2429
seqLen: 100
25-
#shrinkLimit determines how much effort is spent shrinking failing sequences
30+
# The number of test sequences to run.
31+
testLimit: 50000
32+
# How many attemts to run when shrinking the failing sequences.
2633
shrinkLimit: 5000
27-
#coverage controls coverage guided testing
28-
coverage: false
29-
#format can be "text" or "json" for different output (human or machine readable)
30-
format: "text"
31-
#contractAddr is the address of the contract itself
34+
35+
# Stop fuzzing as soon as any property fails and has been shrunk.
36+
stopOnFail: false
37+
# Whether coverage-guided fuzzing is enabled.
38+
coverage: true
39+
# Address of the contract itself
3240
contractAddr: "0x00a329c0648769a73afac7f9381e08fb43dbea72"
33-
#deployer is address of the contract deployer (who often is privileged owner, etc.)
41+
# Address of the contract deployer (who often is privileged owner, etc.)
3442
deployer: "0x30000"
35-
#sender is set of addresses transactions may originate from
43+
# Set of addresses transactions may originate from
3644
sender: ["0x10000", "0x20000", "0x30000"]
37-
#balanceAddr is default balance for addresses
45+
# Default balance for addresses
3846
balanceAddr: 0xffffffff
39-
#balanceContract overrides balanceAddr for the contract address
47+
# Overrides balanceAddr for the contract address
4048
balanceContract: 0
41-
#codeSize max code size for deployed contratcs (default 24576, per EIP-170)
49+
# Max code size for deployed contratcs (default 24576, per EIP-170)
4250
codeSize: 0x6000
43-
#solcArgs allows special args to solc
51+
52+
# Pass additional CLI options to crytic-compile.
53+
# See: https://github.com/crytic/crytic-compile
54+
cryticArgs: []
55+
# Pass additional CLI options to solc.
4456
solcArgs: ""
45-
#solcLibs is solc libraries
57+
# solcLibs is solc libraries
4658
solcLibs: []
47-
#cryticArgs allows special args to crytic
48-
cryticArgs: []
49-
#quiet produces (much) less verbose output
50-
quiet: false
51-
#initialize the blockchain with some data
59+
60+
# Initialize the blockchain with some data
5261
initialize: null
53-
#initialize the blockchain with some predeployed contracts in some addresses
62+
# Initialize the blockchain with some predeployed contracts in some addresses
5463
deployContracts: []
55-
#initialize the blockchain with some bytecode in some addresses
64+
# Initialize the blockchain with some bytecode in some addresses
5665
deployBytecodes: []
57-
#whether ot not to fuzz all contracts
66+
# Whether ot not to fuzz all contracts
5867
allContracts: false
59-
#timeout controls test timeout settings
68+
69+
# Set a timeout to stop fuzzing after N seconds.
6070
timeout: null
61-
#seed not defined by default, is the random seed
62-
#seed: 0
63-
#dictFreq controls how often to use echidna's internal dictionary vs random
64-
#values
71+
72+
# Use to fix the seed for random number generator. If not specified, a new
73+
# random seed value is used every time. A positive integer.
74+
# seed: 0
75+
76+
# How often to use echidna's internal dictionary vs random values while fuzzing.
77+
# Value between 0 and 1.
6578
dictFreq: 0.40
79+
80+
# Defines gas cost at which a property fails
81+
propMaxGas: 8000030
82+
# Gas limit; does not cause failure, but terminates a sequence
83+
testMaxGas: 8000030
84+
# Maximum gas price
85+
maxGasprice: 0
86+
# Maximum value to send to payable functions
87+
maxValue: 100000000000000000000 # 100 eth
88+
# Maximum time between generated txs; default is one week
6689
maxTimeDelay: 604800
67-
#maximum time between generated txs; default is one week
90+
# Maximum number of blocks elapsed between generated txs; default is expected
91+
# increment in one week
6892
maxBlockDelay: 60480
69-
#maximum number of blocks elapsed between generated txs; default is expected increment in one week
70-
# timeout:
71-
#campaign timeout (in seconds)
72-
# list of methods to filter
93+
94+
# List of methods to filter
7395
filterFunctions: []
7496
# by default, blacklist methods in filterFunctions
7597
filterBlacklist: true
76-
# enable or disable ffi HEVM cheatcode
98+
99+
# Enable the ffi HEVM cheatcode. It is disabled by default for security.
100+
# See: https://hevm.dev/controlling-the-unit-testing-environment.html.
77101
allowFFI: false
78-
#directory to save the corpus; by default is disabled
79-
corpusDir: null
80-
# list of file formats to save coverage reports in; default is all possible formats
81-
coverageFormats: ["txt","html","lcov"]
82-
# constants for corpus mutations (for experimentation only)
83-
mutConsts: [1, 1, 1, 1]
84-
# maximum value to send to payable functions
85-
maxValue: 100000000000000000000 # 100 eth
102+
103+
# Configure to perform "on-chain fuzzing".
104+
# See: https://blog.trailofbits.com/2023/07/21/fuzzing-on-chain-contracts-with-echidna/
86105
# URL to fetch contracts over RPC
87106
rpcUrl: null
88-
# block number to use when fetching over RPC
107+
# Block number to use when fetching over RPC
89108
rpcBlock: null
90-
# number of workers
91-
workers: 1
109+
110+
# ===
111+
# NOTE: The experimental options below shouldn't be touched unless you know what you are doing.
112+
# ===
113+
# Constants for corpus mutations (for experimentation only)
114+
mutConsts: [1, 1, 1, 1]
115+
# Perform analysis of maximum gas costs for functions (experimental)
116+
estimateGas: false

0 commit comments

Comments
 (0)