From 92f30c66a75ccb0801a773f57befc0b92845a02f Mon Sep 17 00:00:00 2001 From: Artur Cygan Date: Tue, 27 Feb 2024 13:47:43 +0100 Subject: [PATCH] Update hevm to 0.53.0 (#1189) --- flake.nix | 6 ++--- lib/Echidna.hs | 2 +- lib/Echidna/Campaign.hs | 30 +++++++++++------------ lib/Echidna/Deploy.hs | 12 ++++----- lib/Echidna/Etheno.hs | 18 +++++--------- lib/Echidna/Events.hs | 4 +-- lib/Echidna/Exec.hs | 40 ++++++++++++------------------ lib/Echidna/Shrink.hs | 10 ++++---- lib/Echidna/Solidity.hs | 6 ++--- lib/Echidna/Test.hs | 50 +++++++++++++++++++------------------- lib/Echidna/Transaction.hs | 4 +-- lib/Echidna/Types.hs | 2 +- lib/Echidna/Types/Test.hs | 10 ++++---- lib/Echidna/Types/Tx.hs | 13 +++------- lib/Echidna/UI.hs | 4 +-- lib/Echidna/UI/Report.hs | 22 ++++++++--------- lib/Echidna/UI/Widgets.hs | 10 ++++---- package.yaml | 1 + stack.yaml | 2 +- 19 files changed, 112 insertions(+), 134 deletions(-) diff --git a/flake.nix b/flake.nix index 799da9faf..dd888c6db 100644 --- a/flake.nix +++ b/flake.nix @@ -51,15 +51,15 @@ pkgs.haskellPackages.callCabal2nix "hevm" (pkgs.fetchFromGitHub { owner = "ethereum"; repo = "hevm"; - rev = "release/0.52.0"; - sha256 = "sha256-LCv3m6AbLr9mV7pHj7r08dzsg1UVpQDn0zyJXbzRS2Q="; + rev = "release/0.53.0"; + sha256 = "sha256-/B+McCJBcIxYCmYMcJ5FiwMqPeSCL97WbNusabTUb34="; }) { secp256k1 = pkgs.secp256k1; }); # FIXME: figure out solc situation, it conflicts with the one from # solc-select that is installed with slither, disable tests in the meantime echidna = pkgs: pkgs.haskell.lib.dontCheck ( with pkgs; lib.pipe - (haskellPackages.callCabal2nix "echidna" ./. { inherit (hevm pkgs); }) + (haskellPackages.callCabal2nix "echidna" ./. { hevm = hevm pkgs; }) [ (haskell.lib.compose.addTestToolDepends [ haskellPackages.hpack slither-analyzer solc ]) (haskell.lib.compose.disableCabalFlag "static") diff --git a/lib/Echidna.hs b/lib/Echidna.hs index 1d81268cb..6c32cd966 100644 --- a/lib/Echidna.hs +++ b/lib/Echidna.hs @@ -49,7 +49,7 @@ prepareContract -> NonEmpty FilePath -> Maybe ContractName -> Seed - -> IO (VM RealWorld, World, GenDict) + -> IO (VM Concrete RealWorld, World, GenDict) prepareContract env solFiles specifiedContract seed = do let solConf = env.cfg.solConf contracts = Map.elems env.dapp.solcByName diff --git a/lib/Echidna/Campaign.hs b/lib/Echidna/Campaign.hs index 5c1b9597f..67b3f6b3e 100644 --- a/lib/Echidna/Campaign.hs +++ b/lib/Echidna/Campaign.hs @@ -28,7 +28,7 @@ import System.Random (mkStdGen) import EVM (cheatCode) import EVM.ABI (getAbi, AbiType(AbiAddressType), AbiValue(AbiAddress)) -import EVM.Types hiding (Env, Frame(state)) +import EVM.Types hiding (Env, Frame(state), Gas) import Echidna.ABI import Echidna.Exec @@ -63,7 +63,7 @@ isSuccessful = -- contain minized corpus without sequences that didn't increase the coverage. replayCorpus :: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m, MonadState WorkerState m) - => VM RealWorld -- ^ VM to start replaying from + => VM Concrete RealWorld -- ^ VM to start replaying from -> [(FilePath, [Tx])] -- ^ corpus to replay -> m () replayCorpus vm txSeqs = @@ -85,7 +85,7 @@ runWorker :: (MonadIO m, MonadThrow m, MonadReader Env m) => StateT WorkerState m () -- ^ Callback to run after each state update (for instrumentation) - -> VM RealWorld -- ^ Initial VM state + -> VM Concrete RealWorld -- ^ Initial VM state -> World -- ^ Initial world state -> GenDict -- ^ Generation dictionary -> Int -- ^ Worker id starting from 0 @@ -187,9 +187,9 @@ randseq deployedContracts world = do -- minimized. Stores any useful data in the campaign state if coverage increased. callseq :: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m, MonadState WorkerState m) - => VM RealWorld + => VM Concrete RealWorld -> [Tx] - -> m (VM RealWorld) + -> m (VM Concrete RealWorld) callseq vm txSeq = do env <- ask -- First, we figure out whether we need to execute with or without coverage @@ -261,7 +261,7 @@ callseq vm txSeq = do -- know the return type for each function called. If yes, tries to parse the -- return value as a value of that type. Returns a 'GenDict' style Map. returnValues - :: [(Tx, VMResult RealWorld)] + :: [(Tx, VMResult Concrete RealWorld)] -> (FunctionName -> Maybe AbiType) -> Map AbiType (Set AbiValue) returnValues txResults returnTypeOf = @@ -280,7 +280,7 @@ callseq vm txSeq = do _ -> Nothing -- | Add transactions to the corpus discarding reverted ones - addToCorpus :: Int -> [(Tx, (VMResult RealWorld, Gas))] -> Corpus -> Corpus + addToCorpus :: Int -> [(Tx, (VMResult Concrete RealWorld, Gas))] -> Corpus -> Corpus addToCorpus n res corpus = if null rtxs then corpus else Set.insert (n, rtxs) corpus where rtxs = fst <$> res @@ -289,8 +289,8 @@ callseq vm txSeq = do -- executed, saving the transaction if it finds new coverage. execTxOptC :: (MonadIO m, MonadReader Env m, MonadState WorkerState m, MonadThrow m) - => VM RealWorld -> Tx - -> m ((VMResult RealWorld, Gas), VM RealWorld) + => VM Concrete RealWorld -> Tx + -> m ((VMResult Concrete RealWorld, Gas), VM Concrete RealWorld) execTxOptC vm tx = do ((res, grew), vm') <- runStateT (execTxWithCov tx) vm when grew $ do @@ -305,7 +305,7 @@ execTxOptC vm tx = do -- | Given current `gasInfo` and a sequence of executed transactions, updates -- information on highest gas usage for each call updateGasInfo - :: [(Tx, (VMResult RealWorld, Gas))] + :: [(Tx, (VMResult Concrete RealWorld, Gas))] -> [Tx] -> Map Text (Gas, [Tx]) -> Map Text (Gas, [Tx]) @@ -326,10 +326,10 @@ updateGasInfo ((t, _):ts) tseq gi = updateGasInfo ts (t:tseq) gi -- known solves. evalSeq :: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m, MonadState WorkerState m) - => VM RealWorld -- ^ Initial VM - -> (VM RealWorld -> Tx -> m (result, VM RealWorld)) + => VM Concrete RealWorld -- ^ Initial VM + -> (VM Concrete RealWorld -> Tx -> m (result, VM Concrete RealWorld)) -> [Tx] - -> m ([(Tx, result)], VM RealWorld) + -> m ([(Tx, result)], VM Concrete RealWorld) evalSeq vm0 execFunc = go vm0 [] where go vm executedSoFar toExecute = do -- NOTE: we do reverse here because we build up this list by prepending, @@ -369,8 +369,8 @@ runUpdate f = do -- Then update accordingly, keeping track of how many times we've tried to solve or shrink. updateTest :: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m, MonadState WorkerState m) - => VM RealWorld - -> (VM RealWorld, [Tx]) + => VM Concrete RealWorld + -> (VM Concrete RealWorld, [Tx]) -> EchidnaTest -> m (Maybe EchidnaTest) updateTest vmForShrink (vm, xs) test = do diff --git a/lib/Echidna/Deploy.hs b/lib/Echidna/Deploy.hs index f2b4574a9..fa2e4ae74 100644 --- a/lib/Echidna/Deploy.hs +++ b/lib/Echidna/Deploy.hs @@ -25,16 +25,16 @@ deployContracts :: (MonadIO m, MonadReader Env m, MonadThrow m) => [(Addr, SolcContract)] -> Addr - -> VM RealWorld - -> m (VM RealWorld) + -> VM Concrete RealWorld + -> m (VM Concrete RealWorld) deployContracts cs = deployBytecodes' $ map (\(a, c) -> (a, c.creationCode)) cs deployBytecodes :: (MonadIO m, MonadReader Env m, MonadThrow m) => [(Addr, Text)] -> Addr - -> VM RealWorld - -> m (VM RealWorld) + -> VM Concrete RealWorld + -> m (VM Concrete RealWorld) deployBytecodes cs = deployBytecodes' $ (\(a, bc) -> (a, fromRight (error ("invalid b16 decoding of: " ++ show bc)) $ BS16.decode $ encodeUtf8 bc) @@ -45,8 +45,8 @@ deployBytecodes' :: (MonadIO m, MonadReader Env m, MonadThrow m) => [(Addr, ByteString)] -> Addr - -> VM RealWorld - -> m (VM RealWorld) + -> VM Concrete RealWorld + -> m (VM Concrete RealWorld) deployBytecodes' cs src initialVM = foldM deployOne initialVM cs where deployOne vm (dst, bytecode) = do diff --git a/lib/Echidna/Etheno.hs b/lib/Echidna/Etheno.hs index 967cc6848..0c4f1008b 100644 --- a/lib/Echidna/Etheno.hs +++ b/lib/Echidna/Etheno.hs @@ -121,7 +121,7 @@ matchSignatureAndCreateTx _ _ = [] -- | Main function: takes a filepath where the initialization sequence lives and returns -- | the initialized VM along with a list of Addr's to put in GenConf -loadEthenoBatch :: Bool -> FilePath -> IO (VM RealWorld) +loadEthenoBatch :: Bool -> FilePath -> IO (VM Concrete RealWorld) loadEthenoBatch ffi fp = do bs <- eitherDecodeFileStrict fp case bs of @@ -132,7 +132,7 @@ loadEthenoBatch ffi fp = do vm <- stToIO $ initialVM ffi execStateT initVM vm -initAddress :: MonadState (VM s) m => Addr -> m () +initAddress :: MonadState (VM Concrete s) m => Addr -> m () initAddress addr = do cs <- gets (.env.contracts) if LitAddr addr `member` cs then pure () @@ -144,8 +144,8 @@ initAddress addr = do & set #balance (Lit 100000000000000000000) -- default balance for EOAs in etheno crashWithQueryError - :: (MonadState (VM s) m, MonadFail m, MonadThrow m) - => Query s + :: (MonadState (VM Concrete s) m, MonadFail m, MonadThrow m) + => Query Concrete s -> Etheno -> m () crashWithQueryError q et = @@ -166,7 +166,7 @@ crashWithQueryError q et = -- | Takes a list of Etheno transactions and loads them into the VM, returning the -- | address containing echidna tests -execEthenoTxs :: (MonadIO m, MonadState (VM RealWorld) m, MonadFail m, MonadThrow m) => Etheno -> m () +execEthenoTxs :: (MonadIO m, MonadState (VM Concrete RealWorld) m, MonadFail m, MonadThrow m) => Etheno -> m () execEthenoTxs et = do setupEthenoTx et vm <- get @@ -177,12 +177,6 @@ execEthenoTxs et = do 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. - fromEVM (continue (Case (c > 0))) - runFully vm (HandleEffect (Query q), _) -> crashWithQueryError q et (VMFailure x, _) -> vmExcept x >> M.fail "impossible" (VMSuccess (ConcreteBuf bc), @@ -194,7 +188,7 @@ execEthenoTxs et = do _ -> pure () -- | For an etheno txn, set up VM to execute txn -setupEthenoTx :: (MonadIO m, MonadState (VM RealWorld) m) => Etheno -> m () +setupEthenoTx :: (MonadIO m, MonadState (VM Concrete RealWorld) m) => Etheno -> m () setupEthenoTx (AccountCreated f) = initAddress f -- TODO: improve etheno to include initial balance setupEthenoTx (ContractCreated f c _ _ d v) = diff --git a/lib/Echidna/Events.hs b/lib/Echidna/Events.hs index 75f4bf9a9..7300fbaac 100644 --- a/lib/Echidna/Events.hs +++ b/lib/Echidna/Events.hs @@ -29,7 +29,7 @@ type Events = [Text] emptyEvents :: TreePos Empty a emptyEvents = fromForest [] -extractEvents :: Bool -> DappInfo -> VM s -> Events +extractEvents :: Bool -> DappInfo -> VM Concrete s -> Events extractEvents decodeErrors dappInfo vm = let forest = traceForest vm in maybeToList (decodeRevert decodeErrors vm) @@ -76,7 +76,7 @@ maybeContractNameFromCodeHash info codeHash = contractToName <$> maybeContract where maybeContract = snd <$> Map.lookup codeHash info.solcByHash contractToName c = contractNamePart c.contractName -decodeRevert :: Bool -> VM s -> Maybe Text +decodeRevert :: Bool -> VM Concrete s -> Maybe Text decodeRevert decodeErrors vm = case vm.result of Just (VMFailure (Revert (ConcreteBuf bs))) -> decodeRevertMsg decodeErrors bs diff --git a/lib/Echidna/Exec.hs b/lib/Echidna/Exec.hs index 9f91c3df5..fe1f389f0 100644 --- a/lib/Echidna/Exec.hs +++ b/lib/Echidna/Exec.hs @@ -27,7 +27,7 @@ import EVM.ABI import EVM.Exec (exec, vmForEthrunCreation) import EVM.Fetch qualified import EVM.Format (hexText) -import EVM.Types hiding (Env) +import EVM.Types hiding (Env, Gas) import Echidna.Events (emptyEvents) import Echidna.Onchain (safeFetchContractFrom, safeFetchSlotFrom) @@ -57,16 +57,16 @@ classifyError = \case _ -> UnknownE -- | Extracts the 'Query' if there is one. -getQuery :: VMResult s -> Maybe (Query s) +getQuery :: VMResult Concrete s -> Maybe (Query Concrete s) getQuery (HandleEffect (Query q)) = Just q getQuery _ = Nothing -- | Matches execution errors that just cause a reversion. -pattern Reversion :: VMResult s +pattern Reversion :: VMResult Concrete s pattern Reversion <- VMFailure (classifyError -> RevertE) -- | Matches execution errors caused by illegal behavior. -pattern Illegal :: VMResult s +pattern Illegal :: VMResult Concrete s pattern Illegal <- VMFailure (classifyError -> IllegalE) -- | Given an execution error, throw the appropriate exception. @@ -75,10 +75,10 @@ vmExcept e = throwM $ case VMFailure e of {Illegal -> IllegalExec e; _ -> UnknownFailure e} execTxWith - :: (MonadIO m, MonadState (VM RealWorld) m, MonadReader Env m, MonadThrow m) - => m (VMResult RealWorld) + :: (MonadIO m, MonadState (VM Concrete RealWorld) m, MonadReader Env m, MonadThrow m) + => m (VMResult Concrete RealWorld) -> Tx - -> m (VMResult RealWorld, Gas) + -> m (VMResult Concrete RealWorld, Gas) execTxWith executeTx tx = do vm <- get if hasSelfdestructed vm tx.dst then @@ -180,16 +180,6 @@ execTxWith executeTx tx = do fromEVM (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. - fromEVM (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 @@ -230,9 +220,9 @@ logMsg msg = do -- | Execute a transaction "as normal". execTx :: (MonadIO m, MonadReader Env m, MonadThrow m) - => VM RealWorld + => VM Concrete RealWorld -> Tx - -> m ((VMResult RealWorld, Gas), VM RealWorld) + -> m ((VMResult Concrete RealWorld, Gas), VM Concrete RealWorld) execTx vm tx = runStateT (execTxWith (fromEVM exec) tx) vm -- | A type alias for the context we carry while executing instructions @@ -240,9 +230,9 @@ type CoverageContext = (Bool, Maybe (VMut.IOVector CoverageInfo, Int)) -- | Execute a transaction, logging coverage at every step. execTxWithCov - :: (MonadIO m, MonadState (VM RealWorld) m, MonadReader Env m, MonadThrow m) + :: (MonadIO m, MonadState (VM Concrete RealWorld) m, MonadReader Env m, MonadThrow m) => Tx - -> m ((VMResult RealWorld, Gas), Bool) + -> m ((VMResult Concrete RealWorld, Gas), Bool) execTxWithCov tx = do env <- ask @@ -273,7 +263,7 @@ execTxWithCov tx = do pure r where -- | Repeatedly exec a step and add coverage until we have an end result - loop :: VM RealWorld -> IO (VMResult RealWorld, VM RealWorld) + loop :: VM Concrete RealWorld -> IO (VMResult Concrete RealWorld, VM Concrete RealWorld) loop !vm = case vm.result of Nothing -> do addCoverage vm @@ -281,11 +271,11 @@ execTxWithCov tx = do Just r -> pure (r, vm) -- | Execute one instruction on the EVM - stepVM :: VM RealWorld -> IO (VM RealWorld) + stepVM :: VM Concrete RealWorld -> IO (VM Concrete RealWorld) stepVM = stToIO . execStateT exec1 -- | Add current location to the CoverageMap - addCoverage :: VM RealWorld -> IO () + addCoverage :: VM Concrete RealWorld -> IO () addCoverage !vm = do let (pc, opIx, depth) = currentCovLoc vm contract = currentContract vm @@ -322,7 +312,7 @@ execTxWithCov tx = do currentContract vm = fromMaybe (error "no contract information on coverage") $ vm ^? #env % #contracts % at vm.state.codeContract % _Just -initialVM :: Bool -> ST s (VM s) +initialVM :: Bool -> ST s (VM Concrete s) initialVM ffi = do vm <- vmForEthrunCreation mempty pure $ vm & #block % #timestamp .~ Lit initialTimestamp diff --git a/lib/Echidna/Shrink.hs b/lib/Echidna/Shrink.hs index 9e0574722..fb00d3e05 100644 --- a/lib/Echidna/Shrink.hs +++ b/lib/Echidna/Shrink.hs @@ -9,7 +9,7 @@ import Control.Monad.ST (RealWorld) import Data.Set qualified as Set import Data.List qualified as List -import EVM.Types (VM) +import EVM.Types (VM, VMType(Concrete)) import Echidna.Exec import Echidna.Transaction @@ -22,7 +22,7 @@ import Echidna.Test (getResultFromVM, checkETest) shrinkTest :: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m) - => VM RealWorld + => VM Concrete RealWorld -> EchidnaTest -> m (Maybe EchidnaTest) shrinkTest vm test = do @@ -53,11 +53,11 @@ shrinkTest vm test = do -- generate a smaller one that still solves that test. shrinkSeq :: (MonadIO m, MonadRandom m, MonadReader Env m, MonadThrow m) - => VM RealWorld - -> (VM RealWorld -> m (TestValue, VM RealWorld)) + => VM Concrete RealWorld + -> (VM Concrete RealWorld -> m (TestValue, VM Concrete RealWorld)) -> TestValue -> [Tx] - -> m (Maybe ([Tx], TestValue, VM RealWorld)) + -> m (Maybe ([Tx], TestValue, VM Concrete RealWorld)) shrinkSeq vm f v txs = do txs' <- uniform =<< sequence [shorten, shrunk] (value, vm') <- check txs' vm diff --git a/lib/Echidna/Solidity.hs b/lib/Echidna/Solidity.hs index 653feeed3..2c6b4c266 100644 --- a/lib/Echidna/Solidity.hs +++ b/lib/Echidna/Solidity.hs @@ -114,7 +114,7 @@ staticAddresses SolConf{contractAddr, deployer, sender} = Set.map AbiAddress $ Set.union sender (Set.fromList [contractAddr, deployer, 0x0]) -populateAddresses :: Set Addr -> Integer -> VM s -> VM s +populateAddresses :: Set Addr -> Integer -> VM Concrete s -> VM Concrete s populateAddresses addrs b vm = Set.foldl' (\vm' addr -> if deployed addr @@ -168,7 +168,7 @@ loadSpecified :: Env -> Maybe Text -> [SolcContract] - -> IO (VM RealWorld, [SolSignature], [Text], SignatureMap) + -> IO (VM Concrete RealWorld, [SolSignature], [Text], SignatureMap) loadSpecified env name cs = do let solConf = env.cfg.solConf @@ -359,7 +359,7 @@ prepareHashMaps cs as m = loadSolTests :: Env -> Maybe Text - -> IO (VM RealWorld, World, [EchidnaTest]) + -> IO (VM Concrete RealWorld, World, [EchidnaTest]) loadSolTests env name = do let solConf = env.cfg.solConf let contracts = Map.elems env.dapp.solcByName diff --git a/lib/Echidna/Test.hs b/lib/Echidna/Test.hs index 5972352c3..05837fe41 100644 --- a/lib/Echidna/Test.hs +++ b/lib/Echidna/Test.hs @@ -32,7 +32,7 @@ data CallRes = ResFalse | ResTrue | ResRevert | ResOther deriving (Eq, Show) --- | Given a 'VMResult', classify it assuming it was the result of a call to an Echidna test. -classifyRes :: VMResult s -> CallRes +classifyRes :: VMResult Concrete s -> CallRes classifyRes (VMSuccess b) | forceBuf b == encodeAbiValue (AbiBool True) = ResTrue | forceBuf b == encodeAbiValue (AbiBool False) = ResFalse @@ -40,7 +40,7 @@ classifyRes (VMSuccess b) classifyRes Reversion = ResRevert classifyRes _ = ResOther -getResultFromVM :: VM s -> TxResult +getResultFromVM :: VM Concrete s -> TxResult getResultFromVM vm = case vm.result of Just r -> getResult r @@ -114,7 +114,7 @@ createTests m td ts r ss = case m of updateOpenTest :: EchidnaTest -> [Tx] - -> (TestValue, VM RealWorld, TxResult) + -> (TestValue, VM Concrete RealWorld, TxResult) -> EchidnaTest updateOpenTest test txs (BoolValue False, vm, r) = test { Test.state = Large 0, reproducer = txs, vm = Just vm, result = r } @@ -138,8 +138,8 @@ updateOpenTest _ _ _ = error "Invalid type of test" checkETest :: (MonadIO m, MonadReader Env m, MonadThrow m) => EchidnaTest - -> VM RealWorld - -> m (TestValue, VM RealWorld) + -> VM Concrete RealWorld + -> m (TestValue, VM Concrete RealWorld) checkETest test vm = case test.testType of Exploration -> pure (BoolValue True, vm) -- These values are never used PropertyTest n a -> checkProperty vm n a @@ -151,10 +151,10 @@ checkETest test vm = case test.testType of -- | Given a property test, evaluate it and see if it currently passes. checkProperty :: (MonadIO m, MonadReader Env m, MonadThrow m) - => VM RealWorld + => VM Concrete RealWorld -> Text -> Addr - -> m (TestValue, VM RealWorld) + -> m (TestValue, VM Concrete RealWorld) checkProperty vm f a = do case vm.result of Just (VMSuccess _) -> do @@ -165,11 +165,11 @@ checkProperty vm f a = do runTx :: (MonadIO m, MonadReader Env m, MonadThrow m) - => VM RealWorld + => VM Concrete RealWorld -> Text -> (Addr -> Addr) -> Addr - -> m (VM RealWorld) + -> m (VM Concrete RealWorld) runTx vm f s a = do -- Our test is a regular user-defined test, we exec it and check the result g <- asks (.cfg.txConf.propGas) @@ -177,7 +177,7 @@ runTx vm f s a = do pure vm' --- | Extract a test value from an execution. -getIntFromResult :: Maybe (VMResult RealWorld) -> TestValue +getIntFromResult :: Maybe (VMResult Concrete RealWorld) -> TestValue getIntFromResult (Just (VMSuccess b)) = let bs = forceBuf b in case decodeAbiValue (AbiIntType 256) $ LBS.fromStrict bs of @@ -188,10 +188,10 @@ getIntFromResult _ = IntValue minBound -- | Given a property test, evaluate it and see if it currently passes. checkOptimization :: (MonadIO m, MonadReader Env m, MonadThrow m) - => VM RealWorld + => VM Concrete RealWorld -> Text -> Addr - -> m (TestValue, VM RealWorld) + -> m (TestValue, VM Concrete RealWorld) checkOptimization vm f a = do TestConf _ s <- asks (.cfg.testConf) vm' <- runTx vm f s a @@ -199,10 +199,10 @@ checkOptimization vm f a = do checkStatefulAssertion :: (MonadReader Env m, MonadThrow m) - => VM RealWorld + => VM Concrete RealWorld -> SolSignature -> Addr - -> m (TestValue, VM RealWorld) + -> m (TestValue, VM Concrete RealWorld) checkStatefulAssertion vm sig addr = do dappInfo <- asks (.dapp) let @@ -229,10 +229,10 @@ assumeMagicReturnCode = "FOUNDRY::ASSUME\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" checkDapptestAssertion :: (MonadReader Env m, MonadThrow m) - => VM RealWorld + => VM Concrete RealWorld -> SolSignature -> Addr - -> m (TestValue, VM RealWorld) + -> m (TestValue, VM Concrete RealWorld) checkDapptestAssertion vm sig addr = do let -- Whether the last transaction has any value @@ -253,14 +253,14 @@ checkDapptestAssertion vm sig addr = do checkCall :: (MonadReader Env m, MonadThrow m) - => VM RealWorld - -> (DappInfo -> VM RealWorld -> TestValue) - -> m (TestValue, VM RealWorld) + => VM Concrete RealWorld + -> (DappInfo -> VM Concrete RealWorld -> TestValue) + -> m (TestValue, VM Concrete RealWorld) checkCall vm f = do dappInfo <- asks (.dapp) pure (f dappInfo vm, vm) -checkAssertionTest :: DappInfo -> VM RealWorld -> TestValue +checkAssertionTest :: DappInfo -> VM Concrete RealWorld -> TestValue checkAssertionTest dappInfo vm = let events = extractEvents False dappInfo vm in BoolValue $ null events || not (checkAssertionEvent events) @@ -268,19 +268,19 @@ checkAssertionTest dappInfo vm = checkAssertionEvent :: Events -> Bool checkAssertionEvent = any (T.isPrefixOf "AssertionFailed(") -checkSelfDestructedTarget :: Addr -> DappInfo -> VM RealWorld -> TestValue +checkSelfDestructedTarget :: Addr -> DappInfo -> VM Concrete RealWorld -> TestValue checkSelfDestructedTarget addr _ vm = let selfdestructs' = vm.tx.substate.selfdestructs in BoolValue $ LitAddr addr `notElem` selfdestructs' -checkAnySelfDestructed :: DappInfo -> VM RealWorld -> TestValue +checkAnySelfDestructed :: DappInfo -> VM Concrete RealWorld -> TestValue checkAnySelfDestructed _ vm = BoolValue $ null vm.tx.substate.selfdestructs checkPanicEvent :: T.Text -> Events -> Bool checkPanicEvent n = any (T.isPrefixOf ("Panic(" <> n <> ")")) -checkOverflowTest :: DappInfo -> VM RealWorld-> TestValue +checkOverflowTest :: DappInfo -> VM Concrete RealWorld-> TestValue checkOverflowTest dappInfo vm = let es = extractEvents False dappInfo vm in BoolValue $ null es || not (checkPanicEvent "17" es) @@ -288,9 +288,9 @@ checkOverflowTest dappInfo vm = -- | Reproduce a test saving VM snapshot after every transaction reproduceTest :: (MonadIO m, MonadThrow m, MonadReader Env m) - => VM RealWorld -- ^ Initial VM + => VM Concrete RealWorld -- ^ Initial VM -> EchidnaTest - -> m ([(Tx, VM RealWorld)], VM RealWorld) + -> m ([(Tx, VM Concrete RealWorld)], VM Concrete RealWorld) reproduceTest vm0 test = do let txs = test.reproducer (results, vm) <- go vm0 [] txs diff --git a/lib/Echidna/Transaction.hs b/lib/Echidna/Transaction.hs index d14a66e67..94af0c756 100644 --- a/lib/Echidna/Transaction.hs +++ b/lib/Echidna/Transaction.hs @@ -35,7 +35,7 @@ import Echidna.Types.Tx import Echidna.Types.World (World(..)) import Echidna.Types.Campaign -hasSelfdestructed :: VM s -> Addr -> Bool +hasSelfdestructed :: VM Concrete s -> Addr -> Bool hasSelfdestructed vm addr = LitAddr addr `elem` vm.tx.substate.selfdestructs -- | If half a tuple is zero, make both halves zero. Useful for generating @@ -154,7 +154,7 @@ mutateTx tx = pure tx -- | Given a 'Transaction', set up some 'VM' so it can be executed. Effectively, this just brings -- 'Transaction's \"on-chain\". -setupTx :: (MonadIO m, MonadState (VM RealWorld) m) => Tx -> m () +setupTx :: (MonadIO m, MonadState (VM Concrete RealWorld) m) => Tx -> m () setupTx tx@Tx{call = NoCall} = fromEVM $ do resetState modify' $ \vm -> vm diff --git a/lib/Echidna/Types.hs b/lib/Echidna/Types.hs index 1fbdd829e..e2eccf62a 100644 --- a/lib/Echidna/Types.hs +++ b/lib/Echidna/Types.hs @@ -22,7 +22,7 @@ type Gas = Word64 type MutationConsts a = (a, a, a, a) -- | Transform an EVM action from HEVM to our MonadState VM -fromEVM :: (MonadIO m, MonadState (VM RealWorld) m) => EVM RealWorld r -> m r +fromEVM :: (MonadIO m, MonadState (VM Concrete RealWorld) m) => EVM Concrete RealWorld r -> m r fromEVM evmAction = do vm <- get (result, vm') <- liftIO $ stToIO $ runStateT evmAction vm diff --git a/lib/Echidna/Types/Test.hs b/lib/Echidna/Types/Test.hs index 7b2b58391..2221aa39e 100644 --- a/lib/Echidna/Types/Test.hs +++ b/lib/Echidna/Types/Test.hs @@ -8,21 +8,21 @@ import Data.Aeson import Data.DoubleWord (Int256) import Data.Maybe (maybeToList) import Data.Text (Text) +import GHC.Generics (Generic) import EVM.Dapp (DappInfo) -import EVM.Types (Addr, VM) +import EVM.Types (Addr, VM, VMType(Concrete)) import Echidna.Types (ExecException) import Echidna.Types.Signature (SolSignature) import Echidna.Types.Tx (Tx, TxResult) -import GHC.Generics (Generic) -- | Test mode is parsed from a string type TestMode = String -- | Configuration for the creation of Echidna tests. data TestConf = TestConf - { classifier :: Text -> VM RealWorld -> Bool + { classifier :: Text -> VM Concrete RealWorld -> Bool -- ^ Given a VM state and test name, check if a test just passed (typically -- examining '_result'.) , testSender :: Addr -> Addr @@ -55,7 +55,7 @@ data TestType = PropertyTest Text Addr | OptimizationTest Text Addr | AssertionTest Bool SolSignature Addr - | CallTest Text (DappInfo -> VM RealWorld -> TestValue) + | CallTest Text (DappInfo -> VM Concrete RealWorld -> TestValue) | Exploration instance Eq TestType where @@ -101,7 +101,7 @@ data EchidnaTest = EchidnaTest , value :: TestValue , reproducer :: [Tx] , result :: TxResult - , vm :: Maybe (VM RealWorld) + , vm :: Maybe (VM Concrete RealWorld) } deriving (Show) instance ToJSON EchidnaTest where diff --git a/lib/Echidna/Types/Tx.hs b/lib/Echidna/Types/Tx.hs index ba39bf6e6..fe820580a 100644 --- a/lib/Echidna/Types/Tx.hs +++ b/lib/Echidna/Types/Tx.hs @@ -173,17 +173,14 @@ data TxResult | ErrorCallDepthLimitReached | ErrorMaxCodeSizeExceeded | ErrorMaxInitCodeSizeExceeded - | ErrorMaxIterationsReached | ErrorPrecompileFailure - | ErrorUnexpectedSymbolic - | ErrorJumpIntoSymbolicCode | ErrorDeadPath - | ErrorChoose -- not entirely sure what this is | ErrorWhiffNotUnique | ErrorSMTTimeout | ErrorFFI | ErrorNonceOverflow | ErrorReturnDataOutOfBounds + | ErrorNonexistentFork deriving (Eq, Ord, Show, Enum) $(deriveJSON defaultOptions ''TxResult) @@ -203,19 +200,14 @@ data TxConf = TxConf } -- | Transform a VMResult into a more hash friendly sum type -getResult :: VMResult s -> TxResult +getResult :: VMResult Concrete s -> TxResult getResult = \case VMSuccess b | forceBuf b == encodeAbiValue (AbiBool True) -> ReturnTrue | forceBuf b == encodeAbiValue (AbiBool False) -> ReturnFalse | otherwise -> Stop - HandleEffect (Choose _) -> ErrorChoose HandleEffect (Query _) -> ErrorQuery - Unfinished (UnexpectedSymbolicArg{}) -> ErrorUnexpectedSymbolic - Unfinished (MaxIterationsReached _ _) -> ErrorMaxIterationsReached - Unfinished (JumpIntoSymbolicCode _ _) -> ErrorJumpIntoSymbolicCode - VMFailure (BalanceTooLow _ _) -> ErrorBalanceTooLow VMFailure (UnrecognizedOpcode _) -> ErrorUnrecognizedOpcode VMFailure SelfDestruction -> ErrorSelfDestruction @@ -235,6 +227,7 @@ getResult = \case VMFailure PrecompileFailure -> ErrorPrecompileFailure VMFailure NonceOverflow -> ErrorNonceOverflow VMFailure ReturnDataOutOfBounds -> ErrorReturnDataOutOfBounds + VMFailure (NonexistentFork _) -> ErrorNonexistentFork 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/UI.hs b/lib/Echidna/UI.hs index d4fc3d014..fbe02c099 100644 --- a/lib/Echidna/UI.hs +++ b/lib/Echidna/UI.hs @@ -30,7 +30,7 @@ import UnliftIO ( MonadUnliftIO, newIORef, readIORef, hFlush, stdout , writeIORef, timeout) import UnliftIO.Concurrent hiding (killThread, threadDelay) -import EVM.Types (Addr, Contract, VM, W256) +import EVM.Types (Addr, Contract, VM, VMType(Concrete), W256) import Echidna.ABI import Echidna.Campaign (runWorker, spawnListener) @@ -57,7 +57,7 @@ data UIEvent = -- print non-interactive output in desired format at the end ui :: (MonadCatch m, MonadReader Env m, MonadUnliftIO m) - => VM RealWorld -- ^ Initial VM state + => VM Concrete RealWorld -- ^ Initial VM state -> World -- ^ Initial world state -> GenDict -> [(FilePath, [Tx])] diff --git a/lib/Echidna/UI/Report.hs b/lib/Echidna/UI/Report.hs index 2f2d96a2b..5c2dc564e 100644 --- a/lib/Echidna/UI/Report.hs +++ b/lib/Echidna/UI/Report.hs @@ -27,7 +27,7 @@ import Echidna.Utility (timePrefix) import EVM.Format (showTraceTree, contractNamePart) import EVM.Solidity (SolcContract(..)) -import EVM.Types (W256, VM, Addr, Expr (LitAddr)) +import EVM.Types (W256, VM, VMType(Concrete), Addr, Expr (LitAddr)) ppLogLine :: (LocalTime, CampaignEvent) -> String ppLogLine (time, event@(WorkerEvent workerId _)) = @@ -35,7 +35,7 @@ ppLogLine (time, event@(WorkerEvent workerId _)) = ppLogLine (time, event) = timePrefix time <> " " <> ppCampaignEvent event -ppCampaign :: (MonadIO m, MonadReader Env m) => VM RealWorld -> [WorkerState] -> m String +ppCampaign :: (MonadIO m, MonadReader Env m) => VM Concrete RealWorld -> [WorkerState] -> m String ppCampaign vm workerStates = do tests <- liftIO . readIORef =<< asks (.testsRef) testsPrinted <- ppTests tests @@ -53,7 +53,7 @@ ppCampaign vm workerStates = do -- | Given rules for pretty-printing associated address, and whether to print -- them, pretty-print a 'Transaction'. -ppTx :: MonadReader Env m => VM RealWorld -> Bool -> Tx -> m String +ppTx :: MonadReader Env m => VM Concrete RealWorld -> Bool -> Tx -> m String ppTx _ _ Tx { call = NoCall, delay } = pure $ "*wait*" <> ppDelay delay ppTx vm printName tx = do @@ -70,7 +70,7 @@ ppTx vm printName tx = do <> (if tx.value == 0 then "" else " Value: " <> show tx.value) <> ppDelay tx.delay -contractNameForAddr :: MonadReader Env m => VM RealWorld -> Addr -> m Text +contractNameForAddr :: MonadReader Env m => VM Concrete RealWorld -> Addr -> m Text contractNameForAddr vm addr = do dapp <- asks (.dapp) maybeName <- case Map.lookup (LitAddr addr) (vm ^. #env % #contracts) of @@ -101,14 +101,14 @@ ppCorpus = do pure $ "Corpus size: " <> show (corpusSize corpus) -- | Pretty-print the gas usage information a 'Campaign' has obtained. -ppGasInfo :: MonadReader Env m => VM RealWorld -> [WorkerState] -> m String +ppGasInfo :: MonadReader Env m => VM Concrete RealWorld -> [WorkerState] -> m String ppGasInfo vm workerStates = do let gasInfo = Map.unionsWith max ((.gasInfo) <$> workerStates) items <- mapM (ppGasOne vm) $ sortOn (\(_, (n, _)) -> n) $ toList gasInfo pure $ intercalate "" items -- | Pretty-print the gas usage for a function. -ppGasOne :: MonadReader Env m => VM RealWorld -> (Text, (Gas, [Tx])) -> m String +ppGasOne :: MonadReader Env m => VM Concrete RealWorld -> (Text, (Gas, [Tx])) -> m String ppGasOne _ ("", _) = pure "" ppGasOne vm (func, (gas, txs)) = do let header = "\n" <> unpack func <> " used a maximum of " <> show gas <> " gas\n" @@ -117,7 +117,7 @@ ppGasOne vm (func, (gas, txs)) = do pure $ header <> unlines ((" " <>) <$> prettyTxs) -- | Pretty-print the status of a solved test. -ppFail :: MonadReader Env m => Maybe (Int, Int) -> VM RealWorld -> [Tx] -> m String +ppFail :: MonadReader Env m => Maybe (Int, Int) -> VM Concrete RealWorld -> [Tx] -> m String ppFail _ _ [] = pure "failed with no transactions made ⁉️ " ppFail b vm xs = do let status = case b of @@ -130,7 +130,7 @@ ppFail b vm xs = do <> "Traces: \n" <> T.unpack (showTraceTree dappInfo vm) -- | Pretty-print the status of a solved test. -ppFailWithTraces :: MonadReader Env m => Maybe (Int, Int) -> VM RealWorld -> [(Tx, VM RealWorld)] -> m String +ppFailWithTraces :: MonadReader Env m => Maybe (Int, Int) -> VM Concrete RealWorld -> [(Tx, VM Concrete RealWorld)] -> m String ppFailWithTraces _ _ [] = pure "failed with no transactions made ⁉️ " ppFailWithTraces b finalVM results = do dappInfo <- asks (.dapp) @@ -148,7 +148,7 @@ ppFailWithTraces b finalVM results = do -- | Pretty-print the status of a test. -ppTS :: MonadReader Env m => TestState -> VM RealWorld -> [Tx] -> m String +ppTS :: MonadReader Env m => TestState -> VM Concrete RealWorld -> [Tx] -> m String ppTS (Failed e) _ _ = pure $ "could not evaluate ☣\n " <> show e ppTS Solved vm l = ppFail Nothing vm l ppTS Passed _ _ = pure " passed! 🎉" @@ -158,7 +158,7 @@ ppTS (Large n) vm l = do m <- asks (.cfg.campaignConf.shrinkLimit) ppFail (if n < m then Just (n, m) else Nothing) vm l -ppOPT :: MonadReader Env m => TestState -> VM RealWorld -> [Tx] -> m String +ppOPT :: MonadReader Env m => TestState -> VM Concrete RealWorld -> [Tx] -> m String ppOPT (Failed e) _ _ = pure $ "could not evaluate ☣\n " <> show e ppOPT Solved vm l = ppOptimized Nothing vm l ppOPT Passed _ _ = pure " passed! 🎉" @@ -168,7 +168,7 @@ ppOPT (Large n) vm l = do ppOptimized (if n < m then Just (n, m) else Nothing) vm l -- | Pretty-print the status of a optimized test. -ppOptimized :: MonadReader Env m => Maybe (Int, Int) -> VM RealWorld -> [Tx] -> m String +ppOptimized :: MonadReader Env m => Maybe (Int, Int) -> VM Concrete RealWorld -> [Tx] -> m String ppOptimized _ _ [] = pure "Call sequence:\n(no transactions)" ppOptimized b vm xs = do let status = case b of diff --git a/lib/Echidna/UI/Widgets.hs b/lib/Echidna/UI/Widgets.hs index f11da53c3..1bb9019b3 100644 --- a/lib/Echidna/UI/Widgets.hs +++ b/lib/Echidna/UI/Widgets.hs @@ -36,7 +36,7 @@ import Echidna.UI.Report import Echidna.Utility (timePrefix) import EVM.Format (showTraceTree) -import EVM.Types (Addr, Contract, W256, VM(..)) +import EVM.Types (Addr, Contract, W256, VM(..), VMType(Concrete)) data UIState = UIState { status :: UIStateStatus @@ -291,7 +291,7 @@ tsWidget (Large n) t = do titleWidget :: Widget n titleWidget = str "Call sequence" <+> str ":" -tracesWidget :: MonadReader Env m => VM RealWorld -> m (Widget n) +tracesWidget :: MonadReader Env m => VM Concrete RealWorld -> m (Widget n) tracesWidget vm = do dappInfo <- asks (.dapp) -- TODO: showTraceTree does coloring with ANSI escape codes, we need to strip @@ -306,7 +306,7 @@ failWidget :: MonadReader Env m => Maybe (Int, Int) -> [Tx] - -> VM RealWorld + -> VM Concrete RealWorld -> TestValue -> TxResult -> m (Widget Name, Widget Name) @@ -344,7 +344,7 @@ maxWidget :: MonadReader Env m => Maybe (Int, Int) -> [Tx] - -> VM RealWorld + -> VM Concrete RealWorld -> TestValue -> m (Widget Name, Widget Name) maxWidget _ [] _ _ = pure (failureBadge, str "*no transactions made*") @@ -362,7 +362,7 @@ maxWidget b xs vm v = do str "Current action: " <+> withAttr (attrName "working") (str ("shrinking " ++ progress n m)) -seqWidget :: MonadReader Env m => VM RealWorld -> [Tx] -> m (Widget Name) +seqWidget :: MonadReader Env m => VM Concrete RealWorld -> [Tx] -> m (Widget Name) seqWidget vm xs = do ppTxs <- mapM (ppTx vm $ length (nub $ (.src) <$> xs) /= 1) xs let ordinals = str . printf "%d. " <$> [1 :: Int ..] diff --git a/package.yaml b/package.yaml index ba6aaf2ae..1992f1a40 100644 --- a/package.yaml +++ b/package.yaml @@ -56,6 +56,7 @@ dependencies: language: GHC2021 default-extensions: + - DataKinds - DuplicateRecordFields - LambdaCase - MultiWayIf diff --git a/stack.yaml b/stack.yaml index f82e7e23f..7958d0c76 100644 --- a/stack.yaml +++ b/stack.yaml @@ -5,7 +5,7 @@ packages: extra-deps: - git: https://github.com/ethereum/hevm.git - commit: 91d906b6593f2ba74748fff9a7d34eadf1980ceb + commit: a39b1c07a3f643330f920042eb94a43d7e6454b5 - restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 - s-cargot-0.1.4.0@sha256:61ea1833fbb4c80d93577144870e449d2007d311c34d74252850bb48aa8c31fb,3525