Skip to content

Commit a267e84

Browse files
authored
Merge branch 'master' into fixed-sym-exec
2 parents 79896a0 + d965855 commit a267e84

File tree

16 files changed

+31
-159
lines changed

16 files changed

+31
-159
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Unreleased
2+
* Dropped Etheno support (#1402)
3+
* Dropped `estimateGas` support and auxiliary code (#1403)
4+
15
## 2.2.7
26

37
* feat: show which project is being fuzzed (#1381)

README.md

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ More seriously, Echidna is a Haskell program designed for fuzzing/property-based
1515
* Interactive terminal UI, text-only or JSON output
1616
* Automatic test case minimization for quick triage
1717
* Seamless integration into the development workflow
18-
* Maximum gas usage reporting of the fuzzing campaign
19-
* Support for a complex contract initialization with [Etheno](https://github.com/crytic/etheno) and Truffle
2018

2119
.. and [a beautiful high-resolution handcrafted logo](https://raw.githubusercontent.com/crytic/echidna/master/echidna.png).
2220

@@ -85,7 +83,7 @@ Our tool signals each execution trace in the corpus with the following "line mar
8583

8684
Echidna can test contracts compiled with different smart contract build systems, including [Foundry](https://book.getfoundry.sh/), [Hardhat](https://hardhat.org/), and [Truffle](https://archive.trufflesuite.com/), using [crytic-compile](https://github.com/crytic/crytic-compile). To invoke Echidna with the current compilation framework, use `echidna .`.
8785

88-
On top of that, Echidna supports two modes of testing complex contracts. Firstly, one can [describe an initialization procedure with Truffle and Etheno](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/advanced/end-to-end-testing.md) and use that as the base state for Echidna. Secondly, Echidna can call into any contract with a known ABI by passing in the corresponding Solidity source in the CLI. Use `allContracts: true` in your config to turn this on.
86+
On top of that, Echidna supports two modes of testing complex contracts. Firstly, one can [take advantage of existing network state](https://secure-contracts.com/program-analysis/echidna/advanced/state-network-forking.html) and use that as the base state for Echidna. Secondly, Echidna can call into any contract with a known ABI by passing in the corresponding Solidity source in the CLI. Use `allContracts: true` in your config to turn this on.
8987

9088
### Crash course on Echidna
9189

@@ -124,8 +122,7 @@ Campaign = {
124122
"error" : string?,
125123
"tests" : [Test],
126124
"seed" : number,
127-
"coverage" : Coverage,
128-
"gas_info" : [GasInfo]
125+
"coverage" : Coverage
129126
}
130127
Test = {
131128
"contract" : string,
@@ -144,9 +141,7 @@ Transaction = {
144141
}
145142
```
146143

147-
`Coverage` is a dict describing certain coverage-increasing calls.
148-
Each `GasInfo` entry is a tuple that describes how maximal
149-
gas usage was achieved, and is also not too important. These interfaces are
144+
`Coverage` is a dict describing certain coverage-increasing calls. These interfaces are
150145
subject to change to be slightly more user-friendly at a later date. `testType`
151146
will either be `property` or `assertion`, and `status` always takes on either
152147
`fuzzing`, `shrinking`, `solved`, `passed`, or `error`.

lib/Echidna.hs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,12 @@ prepareContract cfg solFiles buildOutput selectedContract seed = do
9393

9494
loadInitialCorpus :: Env -> IO [(FilePath, [Tx])]
9595
loadInitialCorpus env = do
96-
-- load transactions from init sequence (if any)
9796
case env.cfg.campaignConf.corpusDir of
98-
Nothing -> pure []
99-
Just dir
100-
-> do ctxs1 <- loadTxs (dir </> "reproducers")
101-
ctxs2 <- loadTxs (dir </> "coverage")
102-
pure (ctxs1 ++ ctxs2)
97+
Nothing -> pure []
98+
Just dir -> do
99+
ctxs1 <- loadTxs (dir </> "reproducers")
100+
ctxs2 <- loadTxs (dir </> "coverage")
101+
pure (ctxs1 ++ ctxs2)
103102

104103
mkEnv :: EConfig -> BuildOutput -> [EchidnaTest] -> World -> Maybe SlitherInfo -> IO Env
105104
mkEnv cfg buildOutput tests world slitherInfo = do

lib/Echidna/Campaign.hs

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import Echidna.SymExec.Exploration (exploreContract, getTargetMethodFromTx, getR
4646
import Echidna.SymExec.Verification (verifyMethod)
4747
import Echidna.Test
4848
import Echidna.Transaction
49-
import Echidna.Types (Gas)
5049
import Echidna.Types.Campaign
5150
import Echidna.Types.Corpus (Corpus, corpusSize)
5251
import Echidna.Types.Coverage (coverageStats)
@@ -141,7 +140,6 @@ runSymWorker callback vm dict workerId _ name = do
141140
effectiveGenDict = dict { defSeed = effectiveSeed }
142141
initialState =
143142
WorkerState { workerId
144-
, gasInfo = mempty
145143
, genDict = effectiveGenDict
146144
, newCoverage = False
147145
, ncallseqs = 0
@@ -334,7 +332,6 @@ runFuzzWorker callback vm dict workerId initialCorpus testLimit = do
334332
effectiveGenDict = dict { defSeed = effectiveSeed }
335333
initialState =
336334
WorkerState { workerId
337-
, gasInfo = mempty
338335
, genDict = effectiveGenDict
339336
, newCoverage = False
340337
, ncallseqs = 0
@@ -494,7 +491,7 @@ callseq vm txSeq = do
494491
-- and construct a set to union to the constants table
495492
diffs = Map.fromList [(AbiAddressType, Set.fromList $ AbiAddress . forceAddr <$> newAddrs)]
496493
-- Now we try to parse the return values as solidity constants, and add them to 'GenDict'
497-
resultMap = returnValues (map (\(t, (vr, _)) -> (t, vr)) results) workerState.genDict.rTypes
494+
resultMap = returnValues results workerState.genDict.rTypes
498495
-- union the return results with the new addresses
499496
additions = Map.unionWith Set.union diffs resultMap
500497
-- append to the constants dictionary
@@ -507,11 +504,6 @@ callseq vm txSeq = do
507504
-- Update the worker state
508505
in workerState
509506
{ genDict = updatedDict
510-
-- Update the gas estimation
511-
, gasInfo =
512-
if conf.estimateGas
513-
then updateGasInfo results [] workerState.gasInfo
514-
else workerState.gasInfo
515507
-- Reset the new coverage flag
516508
, newCoverage = False
517509
-- Keep track of the number of calls to `callseq`
@@ -544,7 +536,7 @@ callseq vm txSeq = do
544536
_ -> Nothing
545537

546538
-- | Add transactions to the corpus, discarding reverted ones
547-
addToCorpus :: Int -> [(Tx, (VMResult Concrete RealWorld, Gas))] -> Corpus -> Corpus
539+
addToCorpus :: Int -> [(Tx, VMResult Concrete RealWorld)] -> Corpus -> Corpus
548540
addToCorpus n res corpus =
549541
if null rtxs then corpus else Set.insert (n, rtxs) corpus
550542
where rtxs = fst <$> res
@@ -554,7 +546,7 @@ callseq vm txSeq = do
554546
execTxOptC
555547
:: (MonadIO m, MonadReader Env m, MonadState WorkerState m, MonadThrow m)
556548
=> VM Concrete RealWorld -> Tx
557-
-> m ((VMResult Concrete RealWorld, Gas), VM Concrete RealWorld)
549+
-> m (VMResult Concrete RealWorld, VM Concrete RealWorld)
558550
execTxOptC vm tx = do
559551
((res, grew), vm') <- runStateT (execTxWithCov tx) vm
560552
when grew $ do
@@ -566,25 +558,6 @@ execTxOptC vm tx = do
566558
in workerState { newCoverage = True, genDict = dict' }
567559
pure (res, vm')
568560

569-
-- | Given current `gasInfo` and a sequence of executed transactions, updates
570-
-- information on the highest gas usage for each call
571-
updateGasInfo
572-
:: [(Tx, (VMResult Concrete RealWorld, Gas))]
573-
-> [Tx]
574-
-> Map Text (Gas, [Tx])
575-
-> Map Text (Gas, [Tx])
576-
updateGasInfo [] _ gi = gi
577-
updateGasInfo ((tx@Tx{call = SolCall (f, _)}, (_, used')):txs) tseq gi =
578-
case mused of
579-
Nothing -> rec
580-
Just (used, _) | used' > used -> rec
581-
Just (used, otseq) | (used' == used) && (length otseq > length tseq') -> rec
582-
_ -> updateGasInfo txs tseq' gi
583-
where mused = Map.lookup f gi
584-
tseq' = tx:tseq
585-
rec = updateGasInfo txs tseq' (Map.insert f (used', reverse tseq') gi)
586-
updateGasInfo ((t, _):ts) tseq gi = updateGasInfo ts (t:tseq) gi
587-
588561
-- | Given an initial 'VM' state and a way to run transactions, evaluate a list
589562
-- of transactions, constantly checking if we've solved any tests.
590563
evalSeq

lib/Echidna/Config.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ instance FromJSON EConfigWithUsage where
8989
campaignConfParser = CampaignConf
9090
<$> v ..:? "testLimit" ..!= defaultTestLimit
9191
<*> v ..:? "stopOnFail" ..!= False
92-
<*> v ..:? "estimateGas" ..!= False
9392
<*> v ..:? "seqLen" ..!= defaultSequenceLength
9493
<*> v ..:? "shrinkLimit" ..!= defaultShrinkLimit
9594
<*> (v ..:? "coverage" <&> \case Just False -> Nothing; _ -> Just mempty)

lib/Echidna/Exec.hs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import Echidna.Onchain (safeFetchContractFrom, safeFetchSlotFrom)
3737
import Echidna.SourceMapping (lookupUsingCodehashOrInsert)
3838
import Echidna.SymExec.Symbolic (forceBuf)
3939
import Echidna.Transaction
40-
import Echidna.Types (ExecException(..), Gas, fromEVM, emptyAccount)
40+
import Echidna.Types (ExecException(..), fromEVM, emptyAccount)
4141
import Echidna.Types.Config (Env(..), EConfig(..), UIConf(..), OperationMode(..), OutputFormat(Text))
4242
import Echidna.Types.Coverage (CoverageInfo)
4343
import Echidna.Types.Solidity (SolConf(..))
@@ -84,24 +84,22 @@ execTxWith
8484
:: (MonadIO m, MonadState (VM Concrete RealWorld) m, MonadReader Env m, MonadThrow m)
8585
=> m (VMResult Concrete RealWorld)
8686
-> Tx
87-
-> m (VMResult Concrete RealWorld, Gas)
87+
-> m (VMResult Concrete RealWorld)
8888
execTxWith executeTx tx = do
8989
vm <- get
9090
if hasSelfdestructed vm tx.dst then
91-
pure (VMFailure (Revert (ConcreteBuf "")), 0)
91+
pure $ VMFailure (Revert (ConcreteBuf ""))
9292
else do
9393
#traces .= emptyEvents
9494
vmBeforeTx <- get
9595
setupTx tx
9696
case tx.call of
97-
NoCall -> pure (VMSuccess (ConcreteBuf ""), 0)
97+
NoCall -> pure $ VMSuccess (ConcreteBuf "")
9898
_ -> do
99-
gasLeftBeforeTx <- gets (.state.gas)
10099
vmResult <- runFully
101-
gasLeftAfterTx <- gets (.state.gas)
102100
handleErrorsAndConstruction vmResult vmBeforeTx
103101
fromEVM clearTStorages
104-
pure (vmResult, gasLeftBeforeTx - gasLeftAfterTx)
102+
pure vmResult
105103
where
106104
runFully = do
107105
config <- asks (.cfg)
@@ -240,8 +238,8 @@ execTx
240238
:: (MonadIO m, MonadReader Env m, MonadThrow m)
241239
=> VM Concrete RealWorld
242240
-> Tx
243-
-> m ((VMResult Concrete RealWorld, Gas), VM Concrete RealWorld)
244-
execTx vm tx = runStateT (execTxWith (fromEVM $ exec defaultConfig) tx) vm
241+
-> m (VMResult Concrete RealWorld, VM Concrete RealWorld)
242+
execTx vm tx = runStateT (execTxWith (fromEVM (exec defaultConfig)) tx) vm
245243

246244
-- | A type alias for the context we carry while executing instructions
247245
type CoverageContext = (Bool, Maybe (VMut.IOVector CoverageInfo, Int))
@@ -250,7 +248,7 @@ type CoverageContext = (Bool, Maybe (VMut.IOVector CoverageInfo, Int))
250248
execTxWithCov
251249
:: (MonadIO m, MonadState (VM Concrete RealWorld) m, MonadReader Env m, MonadThrow m)
252250
=> Tx
253-
-> m ((VMResult Concrete RealWorld, Gas), Bool)
251+
-> m (VMResult Concrete RealWorld, Bool)
254252
execTxWithCov tx = do
255253
env <- ask
256254

@@ -263,7 +261,7 @@ execTxWithCov tx = do
263261
-- Update the last valid location with the transaction result
264262
grew' <- liftIO $ case lastLoc of
265263
Just (vec, pc) -> do
266-
let txResultBit = fromEnum $ getResult $ fst r
264+
let txResultBit = fromEnum $ getResult r
267265
VMut.read vec pc >>= \case
268266
(opIx, depths, txResults) | not (txResults `testBit` txResultBit) -> do
269267
VMut.write vec pc (opIx, depths, txResults `setBit` txResultBit)

lib/Echidna/Output/JSON.hs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import EVM.Dapp (DappInfo)
1717

1818
import Echidna.ABI (ppAbiValue, GenDict(..))
1919
import Echidna.Events (Events, extractEvents)
20-
import Echidna.Types (Gas)
2120
import Echidna.Types.Campaign (WorkerState(..))
2221
import Echidna.Types.Config (Env(..))
2322
import Echidna.Types.Coverage (CoverageInfo, mergeCoverageMaps)
@@ -31,7 +30,6 @@ data Campaign = Campaign
3130
, _tests :: [Test]
3231
, seed :: Int
3332
, coverage :: Map String [CoverageInfo]
34-
, gasInfo :: [(Text, (Gas, [Tx]))]
3533
}
3634

3735
instance ToJSON Campaign where
@@ -41,7 +39,6 @@ instance ToJSON Campaign where
4139
, "tests" .= _tests
4240
, "seed" .= seed
4341
, "coverage" .= coverage
44-
, "gas_info" .= gasInfo
4542
]
4643

4744
data Test = Test
@@ -113,7 +110,6 @@ encodeCampaign env workerStates = do
113110
, _tests = mapTest env.dapp <$> tests
114111
, seed = seed
115112
, coverage = Map.mapKeys (("0x" ++) . (`showHex` "")) $ VU.toList <$> frozenCov
116-
, gasInfo = Map.toList $ Map.unionsWith max ((.gasInfo) <$> workerStates)
117113
}
118114

119115
mapTest :: DappInfo -> EchidnaTest -> Test

lib/Echidna/Solidity.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ loadSpecified
173173
loadSpecified env mainContract cs = do
174174
let solConf = env.cfg.solConf
175175

176-
-- Set up initial VM, either with chosen contract or Etheno initialization file
176+
-- Set up initial VM with chosen contract
177177
-- need to use snd to add to ABI dict
178178
initVM <- stToIO $ initialVM solConf.allowFFI
179179
let vm = initVM & #block % #gaslimit .~ unlimitedGasPerBlock

lib/Echidna/Types/Campaign.hs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module Echidna.Types.Campaign where
22

33
import Control.Concurrent (ThreadId)
4-
import Data.Map (Map)
54
import Data.Text (Text)
65
import Data.Word (Word8, Word16)
76
import GHC.Conc (numCapabilities)
@@ -19,8 +18,6 @@ data CampaignConf = CampaignConf
1918
-- ^ Maximum number of function calls to execute while fuzzing
2019
, stopOnFail :: Bool
2120
-- ^ Whether to stop the campaign immediately if any property fails
22-
, estimateGas :: Bool
23-
-- ^ Whether to collect gas usage statistics
2421
, seqLen :: Int
2522
-- ^ Number of calls between state resets (e.g. \"every 10 calls,
2623
-- reset the state to avoid unrecoverable states/save memory\"
@@ -73,8 +70,6 @@ data CampaignConf = CampaignConf
7370
data WorkerState = WorkerState
7471
{ workerId :: !Int
7572
-- ^ Worker ID starting from 0
76-
, gasInfo :: !(Map Text (Gas, [Tx]))
77-
-- ^ Worst case gas (NOTE: we don't always record this)
7873
, genDict :: !GenDict
7974
-- ^ Generation dictionary
8075
, newCoverage :: !Bool
@@ -93,7 +88,6 @@ data WorkerState = WorkerState
9388
initialWorkerState :: WorkerState
9489
initialWorkerState =
9590
WorkerState { workerId = 0
96-
, gasInfo = mempty
9791
, genDict = emptyDict
9892
, newCoverage = False
9993
, ncallseqs = 0

lib/Echidna/UI.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ ui vm dict initialCorpus cliSelectedContract = do
165165
liftIO $ killThread ticker
166166

167167
states <- workerStates workers
168-
liftIO . putStrLn =<< ppCampaign vm states
168+
liftIO . putStrLn =<< ppCampaign states
169169

170170
pure states
171171

@@ -220,7 +220,7 @@ ui vm dict initialCorpus cliSelectedContract = do
220220
JSON ->
221221
liftIO $ BS.putStr =<< Echidna.Output.JSON.encodeCampaign env states
222222
Text -> do
223-
liftIO . putStrLn =<< ppCampaign vm states
223+
liftIO . putStrLn =<< ppCampaign states
224224
None ->
225225
pure ()
226226
pure states

0 commit comments

Comments
 (0)