From a608ef60d54b9630712841f0cc8796ed1a865b17 Mon Sep 17 00:00:00 2001 From: Artur Cygan Date: Tue, 11 Jul 2023 10:27:30 +0200 Subject: [PATCH] Update hevm to 0.51.3 --- flake.nix | 4 +-- lib/Echidna.hs | 3 +- lib/Echidna/Campaign.hs | 4 +-- lib/Echidna/Config.hs | 3 +- lib/Echidna/Deploy.hs | 3 +- lib/Echidna/Etheno.hs | 39 ++++++++++++++--------- lib/Echidna/Events.hs | 4 +-- lib/Echidna/Exec.hs | 27 +++++++++++----- lib/Echidna/Output/Source.hs | 25 +++++++-------- lib/Echidna/RPC.hs | 4 +-- lib/Echidna/Shrink.hs | 2 +- lib/Echidna/Solidity.hs | 38 +++++++++++----------- lib/Echidna/Test.hs | 3 +- lib/Echidna/Transaction.hs | 9 +++--- lib/Echidna/Types.hs | 5 +-- lib/Echidna/Types/Config.hs | 3 +- lib/Echidna/Types/Test.hs | 3 +- lib/Echidna/Types/Tx.hs | 55 ++++++++++++++++---------------- lib/Echidna/Types/World.hs | 1 + lib/Echidna/UI.hs | 3 +- lib/Echidna/UI/Widgets.hs | 3 +- src/Main.hs | 61 +++++++++++++++++++----------------- src/test/Common.hs | 16 +++++----- stack.yaml | 2 +- 24 files changed, 171 insertions(+), 149 deletions(-) diff --git a/flake.nix b/flake.nix index 54aa44c6f..a73236b55 100644 --- a/flake.nix +++ b/flake.nix @@ -42,8 +42,8 @@ pkgs.haskellPackages.callCabal2nix "hevm" (pkgs.fetchFromGitHub { owner = "ethereum"; repo = "hevm"; - rev = "release/0.50.5"; - sha256 = "sha256-Vi6kL1nJdujfS1oePwqks1owVPlS5Dd5hAn0r8Rpw+k="; + rev = "release/0.51.3"; + sha256 = "sha256-H6oURBGoQWSOuPhBB+UKg2UarVzXgv1tmfDBLnOtdhU="; }) { secp256k1 = pkgs.secp256k1; }); echidna = with pkgs; lib.pipe diff --git a/lib/Echidna.hs b/lib/Echidna.hs index 4e943e7b1..63ed44ee5 100644 --- a/lib/Echidna.hs +++ b/lib/Echidna.hs @@ -9,9 +9,10 @@ import Data.Map.Strict qualified as Map import Data.Set qualified as Set import System.FilePath (()) -import EVM hiding (Env) +import EVM (cheatCode) import EVM.ABI (AbiValue(AbiAddress)) import EVM.Solidity (SolcContract(..)) +import EVM.Types hiding (Env) import Echidna.ABI import Echidna.Etheno (loadEtheno, extractFromEtheno) diff --git a/lib/Echidna/Campaign.hs b/lib/Echidna/Campaign.hs index 94d51aaf6..61eb64c5c 100644 --- a/lib/Echidna/Campaign.hs +++ b/lib/Echidna/Campaign.hs @@ -24,9 +24,9 @@ import Data.Set qualified as Set import Data.Text (Text) import System.Random (mkStdGen) -import EVM hiding (Env, Frame(state)) +import EVM (bytecode, cheatCode) import EVM.ABI (getAbi, AbiType(AbiAddressType), AbiValue(AbiAddress)) -import EVM.Types (Addr, Expr(ConcreteBuf)) +import EVM.Types hiding (Env, Frame(state)) import Echidna.ABI import Echidna.Exec diff --git a/lib/Echidna/Config.hs b/lib/Echidna/Config.hs index 05012ea27..575b1a1b4 100644 --- a/lib/Echidna/Config.hs +++ b/lib/Echidna/Config.hs @@ -13,8 +13,7 @@ import Data.Set qualified as Set import Data.Text (isPrefixOf) import Data.Yaml qualified as Y -import EVM (VM(..)) -import EVM.Types (W256) +import EVM.Types (VM(..), W256) import Echidna.Test import Echidna.Types.Campaign diff --git a/lib/Echidna/Deploy.hs b/lib/Echidna/Deploy.hs index 641268494..6e078d968 100644 --- a/lib/Echidna/Deploy.hs +++ b/lib/Echidna/Deploy.hs @@ -11,9 +11,8 @@ import Data.Either (fromRight) import Data.Text (Text, unlines) import Data.Text.Encoding (encodeUtf8) -import EVM hiding (bytecode, Env) import EVM.Solidity -import EVM.Types (Addr) +import EVM.Types hiding (Env) import Echidna.Exec (execTx) import Echidna.Events (extractEvents) diff --git a/lib/Echidna/Etheno.hs b/lib/Echidna/Etheno.hs index d87e0190f..f43611a92 100644 --- a/lib/Echidna/Etheno.hs +++ b/lib/Echidna/Etheno.hs @@ -11,7 +11,7 @@ import Control.Exception (Exception) import Control.Monad (void) import Control.Monad.Catch (MonadThrow, throwM) import Control.Monad.Fail qualified as M (MonadFail(..)) -import Control.Monad.State.Strict (MonadState, get, put, execStateT, gets) +import Control.Monad.State.Strict (MonadState, get, put, execStateT, gets, modify', execState) import Data.Aeson (FromJSON(..), (.:), withObject, eitherDecodeFileStrict) import Data.ByteString.Base16 qualified as BS16 (decode) import Data.ByteString.Char8 (ByteString) @@ -26,7 +26,7 @@ import Text.Read (readMaybe) import EVM import EVM.ABI (AbiType(..), AbiValue(..), decodeAbiValue, selector) import EVM.Exec (exec) -import EVM.Types (Addr, W256, Expr(ConcreteBuf)) +import EVM.Types import Echidna.Exec import Echidna.Transaction @@ -168,19 +168,28 @@ execEthenoTxs :: (MonadState VM m, MonadFail m, MonadThrow m) => Etheno -> m () execEthenoTxs et = do setupEthenoTx et vm <- get - res <- fromEVM exec - case (res, et) of - (_ , AccountCreated _) -> pure () - (Reversion, _) -> void $ put vm - (VMFailure (Query q), _) -> crashWithQueryError q et - (VMFailure x, _) -> vmExcept x >> M.fail "impossible" - (VMSuccess (ConcreteBuf bc), - ContractCreated _ ca _ _ _ _) -> do - #env % #contracts % at ca % _Just % #contractcode .= InitCode mempty mempty - fromEVM $ do - replaceCodeOfSelf (RuntimeCode (ConcreteRuntimeCode bc)) - loadContract ca - _ -> pure () + runFully vm + where + runFully vm = do + res <- fromEVM exec + case (res, et) of + (_ , AccountCreated _) -> pure () + (Reversion, _) -> void $ put vm + (HandleEffect (Query (PleaseAskSMT (Lit c) _ continue)), _) -> do + -- NOTE: this is not a real SMT query, we know it is concrete and can + -- resume right away. It is done this way to support iterations counting + -- in hevm. + modify' $ execState (continue (Case (c > 0))) + runFully vm + (HandleEffect (Query q), _) -> crashWithQueryError q et + (VMFailure x, _) -> vmExcept x >> M.fail "impossible" + (VMSuccess (ConcreteBuf bc), + ContractCreated _ ca _ _ _ _) -> do + #env % #contracts % at ca % _Just % #contractcode .= InitCode mempty mempty + fromEVM $ do + replaceCodeOfSelf (RuntimeCode (ConcreteRuntimeCode bc)) + loadContract ca + _ -> pure () -- | For an etheno txn, set up VM to execute txn setupEthenoTx :: MonadState VM m => Etheno -> m () diff --git a/lib/Echidna/Events.hs b/lib/Echidna/Events.hs index c0d1e24a7..c9c93480a 100644 --- a/lib/Echidna/Events.hs +++ b/lib/Echidna/Events.hs @@ -13,12 +13,12 @@ import Data.Tree (flatten) import Data.Tree.Zipper (fromForest, TreePos, Empty) import Data.Vector (fromList) -import EVM hiding (code) +import EVM (traceForest) import EVM.ABI (Event(..), Indexed(..), decodeAbiValue, AbiType(..), AbiValue(..)) import EVM.Dapp (DappContext(..), DappInfo(..)) import EVM.Format (showValues, showError, contractNamePart) import EVM.Solidity (SolcContract(..)) -import EVM.Types (Expr(ConcreteBuf), W256, maybeLitWord) +import EVM.Types import Echidna.Types.Buffer (forceLit, forceBuf) import Data.ByteString (ByteString) diff --git a/lib/Echidna/Exec.hs b/lib/Echidna/Exec.hs index 0012976aa..37232b7d9 100644 --- a/lib/Echidna/Exec.hs +++ b/lib/Echidna/Exec.hs @@ -22,11 +22,12 @@ import Data.Vector qualified as V import Data.Vector.Unboxed.Mutable qualified as VMut import System.Process (readProcessWithExitCode) -import EVM hiding (Env) +import EVM (bytecode, replaceCodeOfSelf, loadContract, exec1, vmOpIx) import EVM.ABI import EVM.Exec (exec, vmForEthrunCreation) import EVM.Fetch qualified -import EVM.Types (Expr(ConcreteBuf, Lit), hexText) +import EVM.Format (hexText) +import EVM.Types hiding (Env) import Echidna.Events (emptyEvents) import Echidna.RPC (safeFetchContractFrom, safeFetchSlotFrom) @@ -43,7 +44,7 @@ import Echidna.Utility (getTimestamp, timePrefix) data ErrorClass = RevertE | IllegalE | UnknownE -- | Given an execution error, classify it. Mostly useful for nice @pattern@s ('Reversion', 'Illegal'). -classifyError :: Error -> ErrorClass +classifyError :: EvmError -> ErrorClass classifyError = \case OutOfGas _ _ -> RevertE Revert _ -> RevertE @@ -56,8 +57,8 @@ classifyError = \case -- | Extracts the 'Query' if there is one. getQuery :: VMResult -> Maybe Query -getQuery (VMFailure (Query q)) = Just q -getQuery _ = Nothing +getQuery (HandleEffect (Query q)) = Just q +getQuery _ = Nothing -- | Matches execution errors that just cause a reversion. pattern Reversion :: VMResult @@ -68,7 +69,7 @@ pattern Illegal :: VMResult pattern Illegal <- VMFailure (classifyError -> IllegalE) -- | Given an execution error, throw the appropriate exception. -vmExcept :: MonadThrow m => Error -> m () +vmExcept :: MonadThrow m => EvmError -> m () vmExcept e = throwM $ case VMFailure e of {Illegal -> IllegalExec e; _ -> UnknownFailure e} @@ -77,7 +78,7 @@ vmExcept e = throwM $ execTxWith :: (MonadIO m, MonadState s m, MonadReader Env m) => Lens' s VM - -> (Error -> m ()) + -> (EvmError -> m ()) -> m VMResult -> Tx -> m (VMResult, Gas) @@ -120,7 +121,7 @@ execTxWith l onErr executeTx tx = do ret <- liftIO $ safeFetchContractFrom rpcBlock rpcUrl addr case ret of -- TODO: fix hevm to not return an empty contract in case of an error - Just contract | contract.contractcode /= EVM.RuntimeCode (EVM.ConcreteRuntimeCode "") -> do + Just contract | contract.contractcode /= RuntimeCode (ConcreteRuntimeCode "") -> do metaCacheRef <- asks (.metadataCache) metaCache <- liftIO $ readIORef metaCacheRef let bc = forceBuf (contract ^. bytecode) @@ -182,6 +183,16 @@ execTxWith l onErr executeTx tx = do l %= execState (continuation encodedResponse) runFully + Just (PleaseAskSMT (Lit c) _ continue) -> do + -- NOTE: this is not a real SMT query, we know it is concrete and can + -- resume right away. It is done this way to support iterations counting + -- in hevm. + l %= execState (continue (Case (c > 0))) + runFully + + Just q@(PleaseAskSMT {}) -> + error $ "Unexpected SMT query: " <> show q + -- No queries to answer, the tx is fully executed and the result is final _ -> pure vmResult diff --git a/lib/Echidna/Output/Source.hs b/lib/Echidna/Output/Source.hs index 606e816df..668087f97 100644 --- a/lib/Echidna/Output/Source.hs +++ b/lib/Echidna/Output/Source.hs @@ -1,8 +1,11 @@ +{-# LANGUAGE ViewPatterns #-} + module Echidna.Output.Source where import Prelude hiding (writeFile) import Data.Aeson (ToJSON(..), FromJSON(..), withText) +import Data.ByteString qualified as BS import Data.Foldable import Data.List (nub, sort) import Data.Maybe (fromMaybe, mapMaybe) @@ -28,8 +31,6 @@ import Echidna.Types.Coverage (OpIx, unpackTxResults, CoverageMap) import Echidna.Types.Tx (TxResult(..)) import Echidna.Types.Signature (getBytecodeMetadata) -type FilePathText = Text - saveCoverages :: [CoverageFileType] -> Int @@ -78,20 +79,18 @@ coverageFileExtension Txt = ".txt" ppCoveredCode :: CoverageFileType -> SourceCache -> [SolcContract] -> CoverageMap -> IO Text ppCoveredCode fileType sc cs s | null s = pure "Coverage map is empty" | otherwise = do - let allFiles = zipWith (\(srcPath, _rawSource) srcLines -> (srcPath, V.map decodeUtf8 srcLines)) - sc.files - sc.lines - -- ^ Collect all the possible lines from all the files + -- List of covered lines during the fuzzing campaing covLines <- srcMapCov sc s cs - -- ^ List of covered lines during the fuzzing campaing let + -- Collect all the possible lines from all the files + allFiles = (\(path, src) -> (path, V.fromList (decodeUtf8 <$> BS.split 0xa src))) <$> Map.elems sc.files + -- Excludes lines such as comments or blanks runtimeLinesMap = buildRuntimeLinesMap sc cs - -- ^ Excludes lines such as comments or blanks + -- Pretty print individual file coverage ppFile (srcPath, srcLines) = let runtimeLines = fromMaybe mempty $ Map.lookup srcPath runtimeLinesMap marked = markLines fileType srcLines runtimeLines (fromMaybe Map.empty (Map.lookup srcPath covLines)) in T.unlines (changeFileName srcPath : changeFileLines (V.toList marked)) - -- ^ Pretty print individual file coverage topHeader = case fileType of Lcov -> "TN:\n" Html -> "" Txt -> "" -- ^ Text to add to top of the file - changeFileName fn = case fileType of + changeFileName (T.pack -> fn) = case fileType of Lcov -> "SF:" <> fn Html -> "" <> HTML.text fn <> "" Txt -> fn @@ -158,11 +157,11 @@ getMarker ErrorOutOfGas = 'o' getMarker _ = 'e' -- | Given a source cache, a coverage map, a contract returns a list of covered lines -srcMapCov :: SourceCache -> CoverageMap -> [SolcContract] -> IO (Map FilePathText (Map Int [TxResult])) +srcMapCov :: SourceCache -> CoverageMap -> [SolcContract] -> IO (Map FilePath (Map Int [TxResult])) srcMapCov sc covMap contracts = do Map.unionsWith Map.union <$> mapM linesCovered contracts where - linesCovered :: SolcContract -> IO (Map Text (Map Int [TxResult])) + linesCovered :: SolcContract -> IO (Map FilePath (Map Int [TxResult])) linesCovered c = case Map.lookup (getBytecodeMetadata c.runtimeCode) covMap of Just vec -> VU.foldl' (\acc covInfo -> case covInfo of @@ -193,7 +192,7 @@ srcMapForOpLocation contract opIx = -- | Builds a Map from file paths to lines that can be executed, this excludes -- for example lines with comments -buildRuntimeLinesMap :: SourceCache -> [SolcContract] -> Map Text (S.Set Int) +buildRuntimeLinesMap :: SourceCache -> [SolcContract] -> Map FilePath (S.Set Int) buildRuntimeLinesMap sc contracts = Map.fromListWith (<>) [(k, S.singleton v) | (k, v) <- mapMaybe (srcMapCodePos sc) srcMaps] diff --git a/lib/Echidna/RPC.hs b/lib/Echidna/RPC.hs index b3d30ba9f..1759b7a84 100644 --- a/lib/Echidna/RPC.hs +++ b/lib/Echidna/RPC.hs @@ -14,9 +14,9 @@ import Network.HTTP.Simple (HttpException) import System.Environment import Text.Read (readMaybe) -import EVM (Contract(..), ContractCode(RuntimeCode), RuntimeCode (..), initialContract) +import EVM (initialContract) import EVM.Fetch qualified -import EVM.Types (Addr, W256) +import EVM.Types import Echidna.Orphans.JSON () import Echidna.Types (emptyAccount) diff --git a/lib/Echidna/Shrink.hs b/lib/Echidna/Shrink.hs index 95ef52114..cf76c9e54 100644 --- a/lib/Echidna/Shrink.hs +++ b/lib/Echidna/Shrink.hs @@ -8,7 +8,7 @@ import Control.Monad.State.Strict (MonadIO) import Data.Set qualified as Set import Data.List qualified as List -import EVM (VM) +import EVM.Types (VM) import Echidna.Events (extractEvents) import Echidna.Exec diff --git a/lib/Echidna/Solidity.hs b/lib/Echidna/Solidity.hs index 72adaade0..3abae02ad 100644 --- a/lib/Echidna/Solidity.hs +++ b/lib/Echidna/Solidity.hs @@ -1,8 +1,9 @@ +{-# LANGUAGE ViewPatterns #-} + module Echidna.Solidity where import Optics.Core hiding (filtered) -import Control.Arrow (first) import Control.Monad (when, unless, forM_) import Control.Monad.Catch (MonadThrow(..)) import Control.Monad.Extra (whenM) @@ -12,7 +13,6 @@ import Data.List (find, partition, isSuffixOf, (\\)) import Data.List.NonEmpty (NonEmpty((:|))) import Data.List.NonEmpty qualified as NE import Data.List.NonEmpty.Extra qualified as NEE -import Data.Map (Map, keys, unions, member) import Data.Map qualified as Map import Data.Maybe (isJust, isNothing, catMaybes, listToMaybe) import Data.Set (Set) @@ -27,10 +27,10 @@ import System.FilePath (joinPath, splitDirectories, ()) import System.IO (openFile, IOMode(..)) import System.Info (os) -import EVM hiding (Env) +import EVM (initialContract, currentContract) import EVM.ABI import EVM.Solidity -import EVM.Types (Addr, FunctionSelector) +import EVM.Types hiding (Env) import Echidna.ABI ( encodeSig, encodeSigWithName, hashSig, fallback @@ -70,13 +70,16 @@ selectSourceCache _ scs = (_,sc):_ -> sc _ -> error "Empty source cache" -readSolcBatch :: FilePath -> IO (Maybe (Map Text SolcContract, SourceCaches)) +readSolcBatch :: FilePath -> IO (Maybe BuildOutput) readSolcBatch d = do fs <- listDirectory d - mxs <- mapM (\f -> readSolc (d f)) fs - case catMaybes mxs of - [] -> return Nothing - xs -> return $ Just (unions $ map fst xs, map (first keys) xs) + case fs of + [f] -> + readSolc CombinedJSON "" (d f) >>= \case + Right buildOutput -> pure $ Just buildOutput + Left e -> + error $ "Failed to parse combined JSON file " <> (d f) <> "\n" <> e + _ -> error "too many files" -- | Given a list of files, use its extenstion to check if it is a precompiled -- contract or try to compile it and get a list of its contracts and a list of source @@ -84,7 +87,7 @@ readSolcBatch d = do compileContracts :: SolConf -> NonEmpty FilePath - -> IO ([SolcContract], SourceCaches) + -> IO BuildOutput compileContracts solConf fp = do path <- findExecutable "crytic-compile" >>= \case Nothing -> throwM NoCryticCompile @@ -94,7 +97,7 @@ compileContracts solConf fp = do usual = ["--solc-disable-warnings", "--export-format", "solc"] solargs = solConf.solcArgs ++ linkLibraries solConf.solcLibs & (usual ++) . (\sa -> if null sa then [] else ["--solc-args", sa]) - compileOne :: FilePath -> IO ([SolcContract], SourceCaches) + compileOne :: FilePath -> IO BuildOutput compileOne x = do mSolc <- do stderr <- if solConf.quiet @@ -107,18 +110,17 @@ compileContracts solConf fp = do ExitSuccess -> readSolcBatch "crytic-export" ExitFailure _ -> throwM $ CompileFailure out err - maybe (throwM SolcReadFailure) (pure . first toList) mSolc + maybe (throwM SolcReadFailure) pure mSolc -- | OS-specific path to the "null" file, which accepts writes without storing them nullFilePath :: String nullFilePath = if os == "mingw32" then "\\\\.\\NUL" else "/dev/null" -- clean up previous artifacts removeJsonFiles "crytic-export" - cps <- mapM compileOne fp - let (cs, ss) = NE.unzip cps - when (length ss > 1) $ + buildOutputs <- mapM compileOne fp + when (length buildOutputs > 1) $ putStrLn "WARNING: more than one SourceCaches was found after compile. \ \Only the first one will be used." - pure (concat cs, NE.head ss) + pure $ NE.head buildOutputs removeJsonFiles :: FilePath -> IO () removeJsonFiles dir = @@ -145,7 +147,7 @@ populateAddresses addrs b vm = account = (initialContract (RuntimeCode (ConcreteRuntimeCode mempty))) { nonce = 0, balance = fromInteger b } - deployed addr = addr `member` vm.env.contracts + deployed addr = addr `Map.member` vm.env.contracts -- | Address to load the first library addrLibrary :: Addr @@ -366,7 +368,7 @@ loadSolTests -> IO (VM, World, [EchidnaTest]) loadSolTests env fp name = do let solConf = env.cfg.solConf - (contracts, _) <- compileContracts solConf fp + BuildOutput{contracts = Contracts (Map.elems -> contracts)} <- compileContracts solConf fp (vm, funs, testNames, _signatureMap) <- loadSpecified env name contracts let eventMap = Map.unions $ map (.eventMap) contracts diff --git a/lib/Echidna/Test.hs b/lib/Echidna/Test.hs index 45c036605..d5a11b5e2 100644 --- a/lib/Echidna/Test.hs +++ b/lib/Echidna/Test.hs @@ -12,10 +12,9 @@ import Data.ByteString.Lazy qualified as LBS import Data.Text (Text) import Data.Text qualified as T -import EVM hiding (Env) import EVM.ABI (AbiValue(..), AbiType(..), encodeAbiValue, decodeAbiValue) import EVM.Dapp (DappInfo) -import EVM.Types (Addr, Expr (ConcreteBuf, Lit)) +import EVM.Types hiding (Env) import Echidna.ABI import Echidna.Events (Events, extractEvents) diff --git a/lib/Echidna/Transaction.hs b/lib/Echidna/Transaction.hs index e80c7f137..05ad3505d 100644 --- a/lib/Echidna/Transaction.hs +++ b/lib/Echidna/Transaction.hs @@ -16,9 +16,9 @@ import Data.Set (Set) import Data.Set qualified as Set import Data.Vector qualified as V -import EVM hiding (resetState, VMOpts(timestamp, gasprice)) +import EVM (initialContract, loadContract, bytecode) import EVM.ABI (abiValueType) -import EVM.Types (Expr(ConcreteBuf, Lit), Addr, W256, FunctionSelector) +import EVM.Types hiding (VMOpts(timestamp, gasprice)) import Echidna.ABI import Echidna.Types.Random @@ -116,7 +116,7 @@ canShrinkTx Tx { call, gasprice = 0, value = 0, delay = (0, 0) } = canShrinkTx _ = True removeCallTx :: Tx -> Tx -removeCallTx t = Tx NoCall 0 t.src 0 0 0 t.delay +removeCallTx t = Tx NoCall t.src t.dst 0 0 0 t.delay -- | Given a 'Transaction', generate a random \"smaller\" 'Transaction', preserving origin, -- destination, value, and call signature. @@ -173,7 +173,8 @@ setupTx tx@Tx{call} = fromEVM $ do } case call of SolCreate bc -> do - #env % #contracts % at tx.dst .= Just (initialContract (InitCode bc mempty) & set #balance tx.value) + #env % #contracts % at tx.dst .= + Just (initialContract (InitCode bc mempty) & set #balance tx.value) loadContract tx.dst #state % #code .= RuntimeCode (ConcreteRuntimeCode bc) SolCall cd -> do diff --git a/lib/Echidna/Types.hs b/lib/Echidna/Types.hs index cec099a2d..b3b53a8fc 100644 --- a/lib/Echidna/Types.hs +++ b/lib/Echidna/Types.hs @@ -1,12 +1,13 @@ module Echidna.Types where -import EVM (Error, EVM, VM, Contract, initialContract, ContractCode (RuntimeCode), RuntimeCode (ConcreteRuntimeCode)) import Control.Exception (Exception) import Control.Monad.State.Strict (MonadState, runState, get, put) import Data.Word (Word64) +import EVM (initialContract) +import EVM.Types -- | We throw this when our execution fails due to something other than reversion. -data ExecException = IllegalExec Error | UnknownFailure Error +data ExecException = IllegalExec EvmError | UnknownFailure EvmError instance Show ExecException where show (IllegalExec e) = "VM attempted an illegal operation: " ++ show e diff --git a/lib/Echidna/Types/Config.hs b/lib/Echidna/Types/Config.hs index 1250129d2..0098bbce7 100644 --- a/lib/Echidna/Types/Config.hs +++ b/lib/Echidna/Types/Config.hs @@ -9,9 +9,8 @@ import Data.Text (Text) import Data.Time (LocalTime) import Data.Word (Word64) -import EVM (Contract) import EVM.Dapp (DappInfo) -import EVM.Types (Addr, W256) +import EVM.Types (Addr, Contract, W256) import Echidna.Types.Campaign (CampaignConf, CampaignEvent) import Echidna.Types.Corpus (Corpus) diff --git a/lib/Echidna/Types/Test.hs b/lib/Echidna/Types/Test.hs index cf8fd8ce9..9e8958387 100644 --- a/lib/Echidna/Types/Test.hs +++ b/lib/Echidna/Types/Test.hs @@ -5,9 +5,8 @@ import Data.DoubleWord (Int256) import Data.Maybe (maybeToList) import Data.Text (Text) -import EVM (VM) import EVM.Dapp (DappInfo) -import EVM.Types (Addr) +import EVM.Types (Addr, VM) import Echidna.Events (Events) import Echidna.Types (ExecException) diff --git a/lib/Echidna/Types/Tx.hs b/lib/Echidna/Types/Tx.hs index 6a04f2a13..fc9a8fd96 100644 --- a/lib/Echidna/Types/Tx.hs +++ b/lib/Echidna/Types/Tx.hs @@ -16,9 +16,8 @@ import Data.ByteString (ByteString) import Data.Text (Text) import Data.Word (Word64) -import EVM (VMResult(..), Error(..)) import EVM.ABI (encodeAbiValue, AbiValue(..), AbiType) -import EVM.Types (Addr, W256) +import EVM.Types import Echidna.Orphans.JSON () import Echidna.Types.Buffer (forceBuf) @@ -173,6 +172,8 @@ data TxResult | ErrorInvalidMemoryAccess | ErrorCallDepthLimitReached | ErrorMaxCodeSizeExceeded + | ErrorMaxInitCodeSizeExceeded + | ErrorMaxIterationsReached | ErrorPrecompileFailure | ErrorUnexpectedSymbolic | ErrorDeadPath @@ -207,31 +208,31 @@ getResult = \case | forceBuf b == encodeAbiValue (AbiBool False) -> ReturnFalse | otherwise -> Stop - VMFailure (BalanceTooLow _ _) -> ErrorBalanceTooLow - VMFailure (UnrecognizedOpcode _) -> ErrorUnrecognizedOpcode - VMFailure SelfDestruction -> ErrorSelfDestruction - VMFailure StackUnderrun -> ErrorStackUnderrun - VMFailure BadJumpDestination -> ErrorBadJumpDestination - VMFailure (Revert _) -> ErrorRevert - VMFailure (OutOfGas _ _) -> ErrorOutOfGas - VMFailure (BadCheatCode _) -> ErrorBadCheatCode - VMFailure StackLimitExceeded -> ErrorStackLimitExceeded - VMFailure IllegalOverflow -> ErrorIllegalOverflow - VMFailure (Query _) -> ErrorQuery - VMFailure StateChangeWhileStatic -> ErrorStateChangeWhileStatic - VMFailure InvalidFormat -> ErrorInvalidFormat - VMFailure InvalidMemoryAccess -> ErrorInvalidMemoryAccess - VMFailure CallDepthLimitReached -> ErrorCallDepthLimitReached - VMFailure (MaxCodeSizeExceeded _ _) -> ErrorMaxCodeSizeExceeded - VMFailure PrecompileFailure -> ErrorPrecompileFailure - VMFailure (UnexpectedSymbolicArg{}) -> ErrorUnexpectedSymbolic - VMFailure DeadPath -> ErrorDeadPath - VMFailure (Choose _) -> ErrorChoose -- not entirely sure what this is - VMFailure (NotUnique _) -> ErrorWhiffNotUnique - VMFailure SMTTimeout -> ErrorSMTTimeout - VMFailure (FFI _) -> ErrorFFI - VMFailure NonceOverflow -> ErrorNonceOverflow - VMFailure ReturnDataOutOfBounds -> ErrorReturnDataOutOfBounds + HandleEffect (Choose _) -> ErrorChoose + HandleEffect (Query _) -> ErrorQuery + + Unfinished (UnexpectedSymbolicArg{}) -> ErrorUnexpectedSymbolic + Unfinished (MaxIterationsReached _ _) -> ErrorMaxIterationsReached + + VMFailure (BalanceTooLow _ _) -> ErrorBalanceTooLow + VMFailure (UnrecognizedOpcode _) -> ErrorUnrecognizedOpcode + VMFailure SelfDestruction -> ErrorSelfDestruction + VMFailure StackUnderrun -> ErrorStackUnderrun + VMFailure BadJumpDestination -> ErrorBadJumpDestination + VMFailure (Revert _) -> ErrorRevert + VMFailure (OutOfGas _ _) -> ErrorOutOfGas + VMFailure (BadCheatCode _) -> ErrorBadCheatCode + VMFailure StackLimitExceeded -> ErrorStackLimitExceeded + VMFailure IllegalOverflow -> ErrorIllegalOverflow + VMFailure StateChangeWhileStatic -> ErrorStateChangeWhileStatic + VMFailure InvalidFormat -> ErrorInvalidFormat + VMFailure InvalidMemoryAccess -> ErrorInvalidMemoryAccess + VMFailure CallDepthLimitReached -> ErrorCallDepthLimitReached + VMFailure (MaxCodeSizeExceeded _ _) -> ErrorMaxCodeSizeExceeded + VMFailure (MaxInitCodeSizeExceeded _ _) -> ErrorMaxInitCodeSizeExceeded + VMFailure PrecompileFailure -> ErrorPrecompileFailure + VMFailure NonceOverflow -> ErrorNonceOverflow + VMFailure ReturnDataOutOfBounds -> ErrorReturnDataOutOfBounds makeSingleTx :: Addr -> Addr -> W256 -> TxCall -> [Tx] makeSingleTx a d v (SolCall c) = [Tx (SolCall c) a d maxGasPerBlock 0 v (0, 0)] diff --git a/lib/Echidna/Types/World.hs b/lib/Echidna/Types/World.hs index 8e438639a..62236c0a3 100644 --- a/lib/Echidna/Types/World.hs +++ b/lib/Echidna/Types/World.hs @@ -19,3 +19,4 @@ data World = World , payableSigs :: [FunctionSelector] , eventMap :: EventMap } + deriving Show diff --git a/lib/Echidna/UI.hs b/lib/Echidna/UI.hs index e8aadcc35..613c3ef11 100644 --- a/lib/Echidna/UI.hs +++ b/lib/Echidna/UI.hs @@ -32,8 +32,7 @@ import UnliftIO ) import UnliftIO.Concurrent hiding (killThread, threadDelay) -import EVM (VM, Contract) -import EVM.Types (Addr, W256) +import EVM.Types (Addr, Contract, VM, W256) import Echidna.ABI import Echidna.Campaign (runWorker) diff --git a/lib/Echidna/UI/Widgets.hs b/lib/Echidna/UI/Widgets.hs index 64f649515..4eee4ce60 100644 --- a/lib/Echidna/UI/Widgets.hs +++ b/lib/Echidna/UI/Widgets.hs @@ -33,8 +33,7 @@ import Echidna.Types.Tx (Tx(..), TxResult(..)) import Echidna.UI.Report import Echidna.Utility (timePrefix) -import EVM.Types (Addr, W256) -import EVM (Contract) +import EVM.Types (Addr, Contract, W256) data UIState = UIState { status :: UIStateStatus diff --git a/src/Main.hs b/src/Main.hs index 5f4a50887..d8427d199 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ViewPatterns #-} module Main where @@ -21,7 +22,6 @@ import Data.Map qualified as Map import Data.Maybe (fromMaybe, isJust) import Data.Set qualified as Set import Data.Text (Text) -import Data.Text qualified as Text import Data.Time.Clock.System (getSystemTime, systemSeconds) import Data.Vector qualified as Vector import Data.Version (showVersion) @@ -35,10 +35,10 @@ import System.FilePath (()) import System.IO (hPutStrLn, stderr) import System.IO.CodePage (withCP65001) -import EVM (Contract(..), bytecode) +import EVM (bytecode) import EVM.Dapp (dappInfo) -import EVM.Solidity (SolcContract(..), SourceCache(..)) -import EVM.Types (Addr, keccak', W256) +import EVM.Solidity (SolcContract(..), SourceCache(..), BuildOutput(..), Contracts (Contracts)) +import EVM.Types (Addr, Contract(..), keccak', W256) import Echidna import Echidna.Config @@ -53,7 +53,7 @@ import Echidna.UI import Echidna.Output.Source import Echidna.Output.Corpus import Echidna.RPC qualified as RPC -import Echidna.Solidity (compileContracts, selectSourceCache) +import Echidna.Solidity (compileContracts) import Echidna.Utility (measureIO) import Etherscan qualified @@ -87,7 +87,7 @@ main = withUtf8 $ withCP65001 $ do Nothing -> pure (Nothing, Nothing) - (contracts, sourceCaches) <- compileContracts cfg.solConf cliFilePath + buildOutput <- compileContracts cfg.solConf cliFilePath cacheContractsRef <- newIORef $ fromMaybe mempty loadedContractsCache cacheSlotsRef <- newIORef $ fromMaybe mempty loadedSlotsCache cacheMetaRef <- newIORef mempty @@ -98,11 +98,12 @@ main = withUtf8 $ withCP65001 $ do testsRef <- newIORef mempty let - sourceCache = selectSourceCache cliSelectedContract sourceCaches - solcByName = Map.fromList [(c.contractName, c) | c <- contracts] + BuildOutput{ sources = sourceCache + , contracts = Contracts (Map.elems -> contracts) + } = buildOutput env = Env { cfg -- TODO put in real path - , dapp = dappInfo "/" solcByName sourceCache + , dapp = dappInfo "/" buildOutput , metadataCache = cacheMetaRef , fetchContractCache = cacheContractsRef , fetchSlotCache = cacheSlotsRef @@ -194,26 +195,28 @@ main = withUtf8 $ withCP65001 $ do pure $ do src <- srcRet (_, srcmap) <- srcmapRet - let sourceCache = SourceCache - { files = [(Text.pack (show addr), UTF8.fromString src.code)] - , lines = [(Vector.fromList . BS.split 0xa . UTF8.fromString) src.code] - , asts = mempty - } - let solcContract = SolcContract - { runtimeCode = runtimeCode - , creationCode = mempty - , runtimeCodehash = keccak' runtimeCode - , creationCodehash = keccak' mempty - , runtimeSrcmap = mempty - , creationSrcmap = srcmap - , contractName = src.name - , constructorInputs = [] -- error "TODO: mkConstructor abis TODO" - , abiMap = mempty -- error "TODO: mkAbiMap abis" - , eventMap = mempty -- error "TODO: mkEventMap abis" - , errorMap = mempty -- error "TODO: mkErrorMap abis" - , storageLayout = Nothing - , immutableReferences = mempty - } + let + files = Map.singleton 0 (show addr, UTF8.fromString src.code) + sourceCache = SourceCache + { files + , lines = Vector.fromList . BS.split 0xa . snd <$> files + , asts = mempty + } + solcContract = SolcContract + { runtimeCode = runtimeCode + , creationCode = mempty + , runtimeCodehash = keccak' runtimeCode + , creationCodehash = keccak' mempty + , runtimeSrcmap = mempty + , creationSrcmap = srcmap + , contractName = src.name + , constructorInputs = [] -- error "TODO: mkConstructor abis TODO" + , abiMap = mempty -- error "TODO: mkAbiMap abis" + , eventMap = mempty -- error "TODO: mkEventMap abis" + , errorMap = mempty -- error "TODO: mkErrorMap abis" + , storageLayout = Nothing + , immutableReferences = mempty + } pure (sourceCache, solcContract) readFileIfExists :: FilePath -> IO (Maybe BS.ByteString) diff --git a/src/test/Common.hs b/src/test/Common.hs index e62314a7c..a69013c15 100644 --- a/src/test/Common.hs +++ b/src/test/Common.hs @@ -32,7 +32,7 @@ import Data.Function ((&)) import Data.IORef import Data.List.NonEmpty (NonEmpty(..)) import Data.List.Split (splitOn) -import Data.Map (fromList, lookup) +import Data.Map qualified as Map import Data.Maybe (isJust) import Data.Text (Text, pack) import Data.SemVer (Version, version, fromText) @@ -41,7 +41,7 @@ import System.Process (readProcess) import Echidna (prepareContract) import Echidna.Config (parseConfig, defaultConfig) import Echidna.Campaign (runWorker) -import Echidna.Solidity (loadSolTests, compileContracts, selectSourceCache) +import Echidna.Solidity (loadSolTests, compileContracts) import Echidna.Test (checkETest) import Echidna.Types (Gas) import Echidna.Types.Config (Env(..), EConfig(..), EConfigWithUsage(..)) @@ -52,7 +52,7 @@ import Echidna.Types.Test import Echidna.Types.Tx (Tx(..), TxCall(..), call) import EVM.Dapp (dappInfo, emptyDapp) -import EVM.Solidity (SolcContract(..)) +import EVM.Solidity (BuildOutput(..), Contracts (Contracts)) import Control.Concurrent (newChan) import Control.Monad (forM_) @@ -92,9 +92,9 @@ withSolcVersion (Just f) t = do runContract :: FilePath -> Maybe ContractName -> EConfig -> IO (Env, WorkerState) runContract f selectedContract cfg = do seed <- maybe (getRandomR (0, maxBound)) pure cfg.campaignConf.seed - (contracts, sourceCaches) <- compileContracts cfg.solConf (f :| []) - let sourceCache = selectSourceCache selectedContract sourceCaches - let solcByName = fromList [(c.contractName, c) | c <- contracts] + buildOutput <- compileContracts cfg.solConf (f :| []) + let BuildOutput{contracts = Contracts cs} = buildOutput + let contracts = Map.elems cs metadataCache <- newIORef mempty fetchContractCache <- newIORef mempty @@ -104,7 +104,7 @@ runContract f selectedContract cfg = do eventQueue <- newChan testsRef <- newIORef mempty let env = Env { cfg = cfg - , dapp = dappInfo "/" solcByName sourceCache + , dapp = dappInfo "/" buildOutput , metadataCache , fetchContractCache , fetchSlotCache @@ -251,7 +251,7 @@ solvedWithout tx t final = maybe False (all $ (/= tx) . (.call)) <$> solnFor t final getGas :: Text -> WorkerState -> Maybe (Gas, [Tx]) -getGas t camp = lookup t camp.gasInfo +getGas t camp = Map.lookup t camp.gasInfo gasInRange :: Text -> Gas -> Gas -> (Env, WorkerState) -> IO Bool gasInRange t l h (_, workerState) = do diff --git a/stack.yaml b/stack.yaml index 52635e2d2..f85ac186a 100644 --- a/stack.yaml +++ b/stack.yaml @@ -5,7 +5,7 @@ packages: extra-deps: - git: https://github.com/ethereum/hevm.git - commit: 1c43a07a692f4af08d91e2116880af9e44e464ec + commit: 211fff90a32824c39bed48e61d4ef722b8d1bad1 - restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 - s-cargot-0.1.4.0@sha256:61ea1833fbb4c80d93577144870e449d2007d311c34d74252850bb48aa8c31fb,3525