Skip to content

Commit

Permalink
Use the proper type for gas (#956)
Browse files Browse the repository at this point in the history
  • Loading branch information
arcz authored Feb 23, 2023
1 parent 78ce44e commit 9eac604
Show file tree
Hide file tree
Showing 7 changed files with 25 additions and 19 deletions.
19 changes: 10 additions & 9 deletions lib/Echidna/Campaign.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,27 @@ import Data.Text (Text)
import System.Random (mkStdGen)

import EVM (Contract, VM(..), VMResult(..), bytecode)
import qualified EVM (Env(..))
import EVM qualified (Env(..))
import EVM.ABI (getAbi, AbiType(AbiAddressType), AbiValue(AbiAddress))
import EVM.Types (Addr, Expr(ConcreteBuf))

import Echidna.ABI
import Echidna.Events (extractEvents)
import Echidna.Exec
import Echidna.Mutator.Corpus
import Echidna.Shrink (shrinkTest)
import Echidna.Test
import Echidna.Transaction
import Echidna.Shrink (shrinkTest)
import Echidna.Types (Gas)
import Echidna.Types.Buffer (viewBuffer)
import Echidna.Types.Campaign
import Echidna.Types.Config
import Echidna.Types.Corpus (InitialCorpus)
import Echidna.Types.Coverage (coveragePoints)
import Echidna.Types.Test
import Echidna.Types.Buffer (viewBuffer)
import Echidna.Types.Signature (makeBytecodeMemo)
import Echidna.Types.Test
import Echidna.Types.Tx (TxCall(..), Tx(..), getResult, call)
import Echidna.Types.World (World)
import Echidna.Mutator.Corpus
import Echidna.Events (extractEvents)

instance MonadThrow m => MonadThrow (RandT g m) where
throwM = lift . throwM
Expand Down Expand Up @@ -120,7 +121,7 @@ evalSeq vmForShrink e = go [] where

-- | Given current `gasInfo` and a sequence of executed transactions, updates information on highest
-- gas usage for each call
updateGasInfo :: [(Tx, (VMResult, Int))] -> [Tx] -> Map Text (Int, [Tx]) -> Map Text (Int, [Tx])
updateGasInfo :: [(Tx, (VMResult, Gas))] -> [Tx] -> Map Text (Gas, [Tx]) -> Map Text (Gas, [Tx])
updateGasInfo [] _ gi = gi
updateGasInfo ((t@(Tx (SolCall (f, _)) _ _ _ _ _ _), (_, used')):ts) tseq gi =
case mused of
Expand All @@ -135,7 +136,7 @@ updateGasInfo ((t, _):ts) tseq gi = updateGasInfo ts (t:tseq) gi

-- | Execute a transaction, capturing the PC and codehash of each instruction executed, saving the
-- transaction if it finds new coverage.
execTxOptC :: (MonadIO m, MonadState (VM, Campaign) m, MonadThrow m) => Tx -> m (VMResult, Int)
execTxOptC :: (MonadIO m, MonadState (VM, Campaign) m, MonadThrow m) => Tx -> m (VMResult, Gas)
execTxOptC tx = do
(vm, Campaign{_bcMemo, _coverage = oldCov}) <- get
let cov = _2 . coverage
Expand All @@ -155,7 +156,7 @@ execTxOptC tx = do
return res

-- | Given a list of transactions in the corpus, save them discarding reverted transactions
addToCorpus :: MonadState Campaign m => Int -> [(Tx, (VMResult, Int))] -> m ()
addToCorpus :: MonadState Campaign m => Int -> [(Tx, (VMResult, Gas))] -> m ()
addToCorpus n res = unless (null rtxs) $ corpus %= Set.insert (n, rtxs)
where rtxs = fst <$> res

Expand Down
10 changes: 5 additions & 5 deletions lib/Echidna/Exec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import System.Process (readProcessWithExitCode)

import Echidna.Events (emptyEvents)
import Echidna.Transaction
import Echidna.Types (ExecException(..), fromEVM)
import Echidna.Types (ExecException(..), Gas, fromEVM)
import Echidna.Types.Buffer (viewBuffer)
import Echidna.Types.Coverage (CoverageMap)
import Echidna.Types.Signature (BytecodeMemo, lookupBytecodeMetadata)
Expand Down Expand Up @@ -63,7 +63,7 @@ vmExcept e = throwM $ case VMFailure e of {Illegal -> IllegalExec e; _ -> Unknow

-- | Given an error handler `onErr`, an execution strategy `executeTx`, and a transaction `tx`,
-- execute that transaction using the given execution strategy, calling `onErr` on errors.
execTxWith :: (MonadIO m, MonadState s m) => Lens' s VM -> (Error -> m ()) -> m VMResult -> Tx -> m (VMResult, Int)
execTxWith :: (MonadIO m, MonadState s m) => Lens' s VM -> (Error -> m ()) -> m VMResult -> Tx -> m (VMResult, Gas)
execTxWith l onErr executeTx tx = do
vm <- use l
if hasSelfdestructed vm tx.dst then
Expand All @@ -76,7 +76,7 @@ execTxWith l onErr executeTx tx = do
vmResult <- runFully
gasLeftAfterTx <- use $ l . state . gas
handleErrorsAndConstruction vmResult vmBeforeTx
pure (vmResult, fromIntegral $ gasLeftBeforeTx - gasLeftAfterTx)
pure (vmResult, gasLeftBeforeTx - gasLeftAfterTx)
where
runFully = do
vmResult <- executeTx
Expand Down Expand Up @@ -135,15 +135,15 @@ execTxWith l onErr executeTx tx = do
_ -> pure ()

-- | Execute a transaction "as normal".
execTx :: (MonadIO m, MonadState VM m, MonadThrow m) => Tx -> m (VMResult, Int)
execTx :: (MonadIO m, MonadState VM m, MonadThrow m) => Tx -> m (VMResult, Gas)
execTx = execTxWith id vmExcept $ fromEVM exec

-- | Execute a transaction, logging coverage at every step.
execTxWithCov
:: (MonadIO m, MonadState VM m, MonadThrow m)
=> BytecodeMemo
-> Tx
-> m ((VMResult, Int), CoverageMap)
-> m ((VMResult, Gas), CoverageMap)
execTxWithCov memo tx = do
vm <- get
(r, (vm', cm)) <- runStateT (execTxWith _1 vmExcept execCov tx) (vm, mempty)
Expand Down
3 changes: 2 additions & 1 deletion lib/Echidna/Output/JSON.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Numeric (showHex)
import EVM.Types (keccak')

import Echidna.ABI (ppAbiValue, GenDict(..))
import Echidna.Types (Gas)
import Echidna.Types.Coverage (CoverageInfo)
import Echidna.Types.Campaign qualified as C
import Echidna.Types.Test qualified as T
Expand All @@ -27,7 +28,7 @@ data Campaign = Campaign
, _tests :: [Test]
, seed :: Int
, coverage :: Map String [CoverageInfo]
, gasInfo :: [(Text, (Int, [Tx]))]
, gasInfo :: [(Text, (Gas, [Tx]))]
}

instance ToJSON Campaign where
Expand Down
2 changes: 2 additions & 0 deletions lib/Echidna/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module Echidna.Types where
import EVM (Error, EVM, VM)
import Control.Exception (Exception)
import Control.Monad.State.Strict (MonadState, runState, get, put)
import Data.Word (Word64)

-- | We throw this when our execution fails due to something other than reversion.
data ExecException = IllegalExec Error | UnknownFailure Error
Expand All @@ -14,6 +15,7 @@ instance Show ExecException where

instance Exception ExecException

type Gas = Word64

type MutationConsts a = (a, a, a, a)

Expand Down
2 changes: 1 addition & 1 deletion lib/Echidna/Types/Campaign.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ data Campaign = Campaign { _tests :: [EchidnaTest]
-- ^ Tests being evaluated
, _coverage :: CoverageMap
-- ^ Coverage captured (NOTE: we don't always record this)
, _gasInfo :: Map Text (Int, [Tx])
, _gasInfo :: Map Text (Gas, [Tx])
-- ^ Worst case gas (NOTE: we don't always record this)
, _genDict :: GenDict
-- ^ Generation dictionary
Expand Down
3 changes: 2 additions & 1 deletion lib/Echidna/UI/Report.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Data.Text qualified as T
import Echidna.ABI (GenDict(..), encodeSig)
import Echidna.Events (Events)
import Echidna.Pretty (ppTxCall)
import Echidna.Types (Gas)
import Echidna.Types.Campaign
import Echidna.Types.Corpus (Corpus, corpusSize)
import Echidna.Types.Coverage (CoverageMap, scoveragePoints)
Expand Down Expand Up @@ -49,7 +50,7 @@ ppCorpus c | c == mempty = Nothing
| otherwise = Just $ "Corpus size: " ++ show (corpusSize c)

-- | Pretty-print the gas usage for a function.
ppGasOne :: MonadReader EConfig m => (Text, (Int, [Tx])) -> m String
ppGasOne :: MonadReader EConfig m => (Text, (Gas, [Tx])) -> m String
ppGasOne ("", _) = pure ""
ppGasOne (f, (g, xs)) = let pxs = mapM (ppTx $ length (nub $ (.src) <$> xs) /= 1) xs in
(("\n" ++ unpack f ++ " used a maximum of " ++ show g ++ " gas\n Call sequence:\n") ++) . unlines . fmap (" " ++) <$> pxs
Expand Down
5 changes: 3 additions & 2 deletions src/test/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import Echidna.Config (parseConfig, defaultConfig)
import Echidna.Campaign (campaign)
import Echidna.Solidity (loadSolTests)
import Echidna.Test (checkETest)
import Echidna.Types (Gas)
import Echidna.Types.Config (Env(..), EConfig(..), EConfigWithUsage(..))
import Echidna.Types.Campaign (Campaign(..), CampaignConf(..))
import Echidna.Types.Signature (ContractName)
Expand Down Expand Up @@ -183,10 +184,10 @@ solvedWith tx t = maybe False (any $ (== tx) . (.call)) . solnFor t
solvedWithout :: TxCall -> Text -> Campaign -> Bool
solvedWithout tx t = maybe False (all $ (/= tx) . (.call)) . solnFor t

getGas :: Text -> Campaign -> Maybe (Int, [Tx])
getGas :: Text -> Campaign -> Maybe (Gas, [Tx])
getGas t camp = lookup t camp._gasInfo

gasInRange :: Text -> Int -> Int -> Campaign -> Bool
gasInRange :: Text -> Gas -> Gas -> Campaign -> Bool
gasInRange t l h c = case getGas t c of
Just (g, _) -> g >= l && g <= h
_ -> False
Expand Down

0 comments on commit 9eac604

Please sign in to comment.