Skip to content

Commit d965855

Browse files
authored
Merge pull request #1403 from crytic/feat-rm-estimategas
Drop `estimateGas` support
2 parents 88cc5b0 + 2f62a65 commit d965855

File tree

15 files changed

+21
-157
lines changed

15 files changed

+21
-157
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## Unreleased
2-
* Dropped Etheno support
2+
* Dropped Etheno support (#1402)
3+
* Dropped `estimateGas` support and auxiliary code (#1403)
34

45
## 2.2.7
56

README.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +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
1918

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

@@ -123,8 +122,7 @@ Campaign = {
123122
"error" : string?,
124123
"tests" : [Test],
125124
"seed" : number,
126-
"coverage" : Coverage,
127-
"gas_info" : [GasInfo]
125+
"coverage" : Coverage
128126
}
129127
Test = {
130128
"contract" : string,
@@ -143,9 +141,7 @@ Transaction = {
143141
}
144142
```
145143

146-
`Coverage` is a dict describing certain coverage-increasing calls.
147-
Each `GasInfo` entry is a tuple that describes how maximal
148-
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
149145
subject to change to be slightly more user-friendly at a later date. `testType`
150146
will either be `property` or `assertion`, and `status` always takes on either
151147
`fuzzing`, `shrinking`, `solved`, `passed`, or `error`.

lib/Echidna/Campaign.hs

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import Echidna.Symbolic (forceAddr)
4040
import Echidna.SymExec (createSymTx)
4141
import Echidna.Test
4242
import Echidna.Transaction
43-
import Echidna.Types (Gas)
4443
import Echidna.Types.Campaign
4544
import Echidna.Types.Corpus (Corpus, corpusSize)
4645
import Echidna.Types.Coverage (coverageStats)
@@ -130,7 +129,6 @@ runSymWorker callback vm dict workerId initialCorpus name = do
130129
effectiveGenDict = dict { defSeed = effectiveSeed }
131130
initialState =
132131
WorkerState { workerId
133-
, gasInfo = mempty
134132
, genDict = effectiveGenDict
135133
, newCoverage = False
136134
, ncallseqs = 0
@@ -208,7 +206,6 @@ runFuzzWorker callback vm dict workerId initialCorpus testLimit = do
208206
effectiveGenDict = dict { defSeed = effectiveSeed }
209207
initialState =
210208
WorkerState { workerId
211-
, gasInfo = mempty
212209
, genDict = effectiveGenDict
213210
, newCoverage = False
214211
, ncallseqs = 0
@@ -368,7 +365,7 @@ callseq vm txSeq = do
368365
-- and construct a set to union to the constants table
369366
diffs = Map.fromList [(AbiAddressType, Set.fromList $ AbiAddress . forceAddr <$> newAddrs)]
370367
-- Now we try to parse the return values as solidity constants, and add them to 'GenDict'
371-
resultMap = returnValues (map (\(t, (vr, _)) -> (t, vr)) results) workerState.genDict.rTypes
368+
resultMap = returnValues results workerState.genDict.rTypes
372369
-- union the return results with the new addresses
373370
additions = Map.unionWith Set.union diffs resultMap
374371
-- append to the constants dictionary
@@ -381,11 +378,6 @@ callseq vm txSeq = do
381378
-- Update the worker state
382379
in workerState
383380
{ genDict = updatedDict
384-
-- Update the gas estimation
385-
, gasInfo =
386-
if conf.estimateGas
387-
then updateGasInfo results [] workerState.gasInfo
388-
else workerState.gasInfo
389381
-- Reset the new coverage flag
390382
, newCoverage = False
391383
-- Keep track of the number of calls to `callseq`
@@ -418,7 +410,7 @@ callseq vm txSeq = do
418410
_ -> Nothing
419411

420412
-- | Add transactions to the corpus, discarding reverted ones
421-
addToCorpus :: Int -> [(Tx, (VMResult Concrete RealWorld, Gas))] -> Corpus -> Corpus
413+
addToCorpus :: Int -> [(Tx, VMResult Concrete RealWorld)] -> Corpus -> Corpus
422414
addToCorpus n res corpus =
423415
if null rtxs then corpus else Set.insert (n, rtxs) corpus
424416
where rtxs = fst <$> res
@@ -428,7 +420,7 @@ callseq vm txSeq = do
428420
execTxOptC
429421
:: (MonadIO m, MonadReader Env m, MonadState WorkerState m, MonadThrow m)
430422
=> VM Concrete RealWorld -> Tx
431-
-> m ((VMResult Concrete RealWorld, Gas), VM Concrete RealWorld)
423+
-> m (VMResult Concrete RealWorld, VM Concrete RealWorld)
432424
execTxOptC vm tx = do
433425
((res, grew), vm') <- runStateT (execTxWithCov tx) vm
434426
when grew $ do
@@ -440,25 +432,6 @@ execTxOptC vm tx = do
440432
in workerState { newCoverage = True, genDict = dict' }
441433
pure (res, vm')
442434

443-
-- | Given current `gasInfo` and a sequence of executed transactions, updates
444-
-- information on the highest gas usage for each call
445-
updateGasInfo
446-
:: [(Tx, (VMResult Concrete RealWorld, Gas))]
447-
-> [Tx]
448-
-> Map Text (Gas, [Tx])
449-
-> Map Text (Gas, [Tx])
450-
updateGasInfo [] _ gi = gi
451-
updateGasInfo ((tx@Tx{call = SolCall (f, _)}, (_, used')):txs) tseq gi =
452-
case mused of
453-
Nothing -> rec
454-
Just (used, _) | used' > used -> rec
455-
Just (used, otseq) | (used' == used) && (length otseq > length tseq') -> rec
456-
_ -> updateGasInfo txs tseq' gi
457-
where mused = Map.lookup f gi
458-
tseq' = tx:tseq
459-
rec = updateGasInfo txs tseq' (Map.insert f (used', reverse tseq') gi)
460-
updateGasInfo ((t, _):ts) tseq gi = updateGasInfo ts (t:tseq) gi
461-
462435
-- | Given an initial 'VM' state and a way to run transactions, evaluate a list
463436
-- of transactions, constantly checking if we've solved any tests.
464437
evalSeq

lib/Echidna/Config.hs

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

lib/Echidna/Exec.hs

Lines changed: 8 additions & 10 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.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,7 +238,7 @@ execTx
240238
:: (MonadIO m, MonadReader Env m, MonadThrow m)
241239
=> VM Concrete RealWorld
242240
-> Tx
243-
-> m ((VMResult Concrete RealWorld, Gas), VM Concrete RealWorld)
241+
-> m (VMResult Concrete RealWorld, VM Concrete RealWorld)
244242
execTx vm tx = runStateT (execTxWith (fromEVM (exec defaultConfig)) tx) vm
245243

246244
-- | A type alias for the context we carry while executing instructions
@@ -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
@@ -112,7 +109,6 @@ encodeCampaign env workerStates = do
112109
, _tests = mapTest env.dapp <$> tests
113110
, seed = seed
114111
, coverage = Map.mapKeys (("0x" ++) . (`showHex` "")) $ VU.toList <$> frozenCov
115-
, gasInfo = Map.toList $ Map.unionsWith max ((.gasInfo) <$> workerStates)
116112
}
117113

118114
mapTest :: DappInfo -> EchidnaTest -> Test

lib/Echidna/Types/Campaign.hs

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

33
import Control.Concurrent (ThreadId)
44
import Data.Aeson
5-
import Data.Map (Map)
65
import Data.Text (Text)
76
import Data.Text qualified as T
87
import Data.Word (Word8, Word16)
@@ -20,8 +19,6 @@ data CampaignConf = CampaignConf
2019
-- ^ Maximum number of function calls to execute while fuzzing
2120
, stopOnFail :: Bool
2221
-- ^ Whether to stop the campaign immediately if any property fails
23-
, estimateGas :: Bool
24-
-- ^ Whether to collect gas usage statistics
2522
, seqLen :: Int
2623
-- ^ Number of calls between state resets (e.g. \"every 10 calls,
2724
-- reset the state to avoid unrecoverable states/save memory\"
@@ -158,8 +155,6 @@ ppWorkerEvent = \case
158155
data WorkerState = WorkerState
159156
{ workerId :: !Int
160157
-- ^ Worker ID starting from 0
161-
, gasInfo :: !(Map Text (Gas, [Tx]))
162-
-- ^ Worst case gas (NOTE: we don't always record this)
163158
, genDict :: !GenDict
164159
-- ^ Generation dictionary
165160
, newCoverage :: !Bool
@@ -178,7 +173,6 @@ data WorkerState = WorkerState
178173
initialWorkerState :: WorkerState
179174
initialWorkerState =
180175
WorkerState { workerId = 0
181-
, gasInfo = mempty
182176
, genDict = emptyDict
183177
, newCoverage = False
184178
, ncallseqs = 0

lib/Echidna/UI.hs

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

165165
states <- workerStates workers
166-
liftIO . putStrLn =<< ppCampaign vm states
166+
liftIO . putStrLn =<< ppCampaign states
167167

168168
pure states
169169

@@ -218,7 +218,7 @@ ui vm dict initialCorpus cliSelectedContract = do
218218
JSON ->
219219
liftIO $ BS.putStr =<< Echidna.Output.JSON.encodeCampaign env states
220220
Text -> do
221-
liftIO . putStrLn =<< ppCampaign vm states
221+
liftIO . putStrLn =<< ppCampaign states
222222
None ->
223223
pure ()
224224
pure states

lib/Echidna/UI/Report.hs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import Control.Monad (forM)
44
import Control.Monad.Reader (MonadReader, MonadIO (liftIO), asks, ask)
55
import Control.Monad.ST (RealWorld)
66
import Data.IORef (readIORef, atomicModifyIORef')
7-
import Data.List (intercalate, nub, sortOn)
8-
import Data.Map (toList)
7+
import Data.List (nub)
98
import Data.Map qualified as Map
109
import Data.Maybe (catMaybes, fromJust, fromMaybe)
1110
import Data.Text (Text, unpack)
@@ -17,7 +16,6 @@ import Echidna.ABI (GenDict(..), encodeSig)
1716
import Echidna.Pretty (ppTxCall)
1817
import Echidna.SourceMapping (findSrcByMetadata, lookupCodehash)
1918
import Echidna.Symbolic (forceWord)
20-
import Echidna.Types (Gas)
2119
import Echidna.Types.Campaign
2220
import Echidna.Types.Config
2321
import Echidna.Types.Corpus (corpusSize)
@@ -53,18 +51,16 @@ ppSeed :: [WorkerState] -> String
5351
ppSeed [] = "unknown" -- should not happen
5452
ppSeed (campaign:_) = show campaign.genDict.defSeed
5553

56-
ppCampaign :: (MonadIO m, MonadReader Env m) => VM Concrete RealWorld -> [WorkerState] -> m String
57-
ppCampaign vm workerStates = do
54+
ppCampaign :: (MonadIO m, MonadReader Env m) => [WorkerState] -> m String
55+
ppCampaign workerStates = do
5856
tests <- liftIO . traverse readIORef =<< asks (.testRefs)
5957
testsPrinted <- ppTests tests
60-
gasInfoPrinted <- ppGasInfo vm workerStates
6158
coveragePrinted <- ppCoverage
6259
let seedPrinted = "Seed: " <> ppSeed workerStates
6360
corpusPrinted <- ppCorpus
6461
let callsPrinted = ppTotalCalls workerStates
6562
pure $ unlines
6663
[ testsPrinted
67-
, gasInfoPrinted
6864
, coveragePrinted
6965
, corpusPrinted
7066
, seedPrinted
@@ -141,22 +137,6 @@ ppCorpus = do
141137
corpus <- liftIO . readIORef =<< asks (.corpusRef)
142138
pure $ "Corpus size: " <> show (corpusSize corpus)
143139

144-
-- | Pretty-print the gas usage information a 'Campaign' has obtained.
145-
ppGasInfo :: (MonadReader Env m, MonadIO m) => VM Concrete RealWorld -> [WorkerState] -> m String
146-
ppGasInfo vm workerStates = do
147-
let gasInfo = Map.unionsWith max ((.gasInfo) <$> workerStates)
148-
items <- mapM (ppGasOne vm) $ sortOn (\(_, (n, _)) -> n) $ toList gasInfo
149-
pure $ intercalate "" items
150-
151-
-- | Pretty-print the gas usage for a function.
152-
ppGasOne :: (MonadReader Env m, MonadIO m) => VM Concrete RealWorld -> (Text, (Gas, [Tx])) -> m String
153-
ppGasOne _ ("", _) = pure ""
154-
ppGasOne vm (func, (gas, txs)) = do
155-
let header = "\n" <> unpack func <> " used a maximum of " <> show gas <> " gas\n"
156-
<> " Call sequence:\n"
157-
prettyTxs <- mapM (ppTx vm $ length (nub $ (.src) <$> txs) /= 1) txs
158-
pure $ header <> unlines ((" " <>) <$> prettyTxs)
159-
160140
-- | Pretty-print the status of a solved test.
161141
ppFail :: (MonadReader Env m, MonadIO m) => Maybe (Int, Int) -> VM Concrete RealWorld -> [Tx] -> m String
162142
ppFail _ _ [] = pure "failed with no transactions made ⁉️ "

src/test/Common.hs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ module Common
1414
, solvedWith
1515
, solvedWithout
1616
, solvedUsing
17-
, getGas
18-
, gasInRange
1917
, countCorpus
2018
, overrideQuiet
2119
, loadSolTests
@@ -44,7 +42,6 @@ import Echidna.Config (parseConfig, defaultConfig)
4442
import Echidna.Campaign (runWorker)
4543
import Echidna.Solidity (selectMainContract, mkTests, loadSpecified, compileContracts)
4644
import Echidna.Test (checkETest)
47-
import Echidna.Types (Gas)
4845
import Echidna.Types.Config (Env(..), EConfig(..), EConfigWithUsage(..))
4946
import Echidna.Types.Campaign
5047
import Echidna.Types.Signature (ContractName)
@@ -237,15 +234,6 @@ solvedWithout :: TxCall -> Text -> (Env, WorkerState) -> IO Bool
237234
solvedWithout tx t final =
238235
maybe False (all $ (/= tx) . (.call)) <$> solnFor t final
239236

240-
getGas :: Text -> WorkerState -> Maybe (Gas, [Tx])
241-
getGas t camp = Map.lookup t camp.gasInfo
242-
243-
gasInRange :: Text -> Gas -> Gas -> (Env, WorkerState) -> IO Bool
244-
gasInRange t l h (_, workerState) = do
245-
pure $ case getGas t workerState of
246-
Just (g, _) -> g >= l && g <= h
247-
_ -> False
248-
249237
countCorpus :: Int -> (Env, WorkerState) -> IO Bool
250238
countCorpus n (env, _) = do
251239
corpus <- readIORef env.corpusRef

0 commit comments

Comments
 (0)