Update hevm to 9ba5e52fc7ec7ae6f7f3a25d5ee426625d2aa9d (#1487)

* Update `hevm` to `9ba5e52fc7ec7ae6f7f3a25d5ee426625d2aa9d`

* CLAUDE.md: document import order

* tree-wide: sort imports

* Fix symbolic tests after hevm update

* update the fetching code for the new hevm

* improved rpc fetching

---------

Co-authored-by: gustavo-grieco <[email protected]>
This commit is contained in:
Emilio López
2025-12-12 14:59:25 -03:00
committed by GitHub
co-authored by gustavo-grieco
parent 6c3fe007a2
commit a31c5f8c26
56 changed files with 302 additions and 298 deletions
+21
View File
@@ -72,6 +72,27 @@ cabal --enable-profiling run echidna -- contract.sol +RTS -p -s
- Main config types in `lib/Echidna/Types/Config.hs`
- Supports campaign, UI, transaction, and Solidity-specific settings
## Code Style
### Import Organization
Imports should be organized into three groups, separated by blank lines, with imports **alphabetically ordered** within each group:
1. **System and 3rd-party imports**: Standard library and external dependencies (alphabetically ordered)
2. **EVM imports**: Modules from `hevm` (e.g., `EVM.*`, `EVM.Types.*`) (alphabetically ordered)
3. **Echidna imports**: Internal modules from this project (e.g., `Echidna.*`) (alphabetically ordered)
Example:
```haskell
import Control.Monad (when)
import Data.Text (Text)
import EVM (VM)
import EVM.Types (Addr)
import Echidna.ABI (genAbiCall)
import Echidna.Types.Campaign (Campaign)
```
## Important Development Notes
- The project uses GHC2021 language standard with several extensions enabled
+2 -2
View File
@@ -54,8 +54,8 @@
(pkgs.haskellPackages.callCabal2nix "hevm" (pkgs.fetchFromGitHub {
owner = "ethereum";
repo = "hevm";
rev = "0d9e2744903d160b175cd9e727660b493d9fac6f";
sha256 = "sha256-SYqhjlvGKdWf55JjGZ8BPFtXqbkL81os5FB9j4Nj40A=";
rev = "9ba5e52fc7ec7ae6f7f3a25d5ee426625d2aa9d3";
sha256 = "sha256-5ZWsXtmZsMw2el4cuR9T+qrySTra6Lcaty/RrLOQ2hU=";
}) { secp256k1 = pkgs.secp256k1; })
([
pkgs.haskell.lib.compose.dontCheck
+8 -9
View File
@@ -3,7 +3,6 @@ module Echidna where
import Control.Concurrent (newChan)
import Control.Monad.Catch (MonadThrow(..))
import Control.Monad.IO.Class (liftIO)
import Control.Monad.ST (RealWorld)
import Data.IORef (newIORef)
import Data.List (find, nub)
import Data.List.NonEmpty (NonEmpty)
@@ -11,33 +10,33 @@ import Data.List.NonEmpty qualified as NE
import Data.Map.Strict qualified as Map
import Data.Maybe (mapMaybe)
import Data.Set qualified as Set
import Data.Text qualified as T
import System.FilePath ((</>))
import System.IO (stderr, hPutStrLn)
import EVM (cheatCode)
import EVM.ABI (AbiValue(AbiAddress))
import EVM.Dapp (dappInfo)
import EVM.Effects (TTY(..), ReadConfig(..), defaultConfig)
import EVM.Fetch qualified
import EVM.Solidity (BuildOutput(..), Contracts(Contracts), Method(..), Mutability(..), SolcContract(..))
import EVM.Types hiding (Env)
import EVM.Effects (TTY(..), ReadConfig(..), defaultConfig)
import Data.Text qualified as T
import System.IO (stderr, hPutStrLn)
import Echidna.ABI
import Echidna.Onchain as Onchain
import Echidna.Output.Corpus
import Echidna.SourceMapping (findSrcForReal)
import Echidna.SourceAnalysis.Slither
import Echidna.Solidity
import Echidna.SourceAnalysis.Slither
import Echidna.SourceMapping (findSrcForReal)
import Echidna.SymExec.Symbolic (forceAddr)
import Echidna.Types.Campaign
import Echidna.Types.Config
import Echidna.Types.Random
import Echidna.Types.Signature (ContractName)
import Echidna.Types.Solidity
import Echidna.Types.Test (EchidnaTest)
import Echidna.Types.Tx
import Echidna.Types.World
import Echidna.Types.Test (EchidnaTest)
import Echidna.Types.Signature (ContractName)
-- | This function is used to prepare, process, compile and initialize smart contracts for testing.
-- It takes:
@@ -56,7 +55,7 @@ prepareContract
-> BuildOutput
-> Maybe ContractName
-> Seed
-> IO (VM Concrete RealWorld, Env, GenDict)
-> IO (VM Concrete, Env, GenDict)
prepareContract cfg solFiles buildOutput selectedContract seed = do
let solConf = cfg.solConf
(Contracts contractMap) = buildOutput.contracts
+5 -5
View File
@@ -7,19 +7,19 @@ import Data.Binary.Put (runPut, putWord32be)
import Data.BinaryWord (unsignedWord)
import Data.Bits (bit)
import Data.Bool (bool)
import Data.ByteString.Lazy as BSLazy (toStrict)
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy as BSLazy (toStrict)
import Data.DoubleWord (Int256, Word256)
import Data.Foldable (toList)
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Set (Set)
import Data.Set qualified as Set
import Data.List (intercalate)
import Data.List.NonEmpty (NonEmpty)
import Data.List.NonEmpty qualified as NE
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe (fromMaybe, catMaybes)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding (encodeUtf8)
+19 -20
View File
@@ -11,16 +11,15 @@ import Control.Monad.Random.Strict (MonadRandom, RandT, evalRandT)
import Control.Monad.Reader (MonadReader, asks, liftIO, ask)
import Control.Monad.State.Strict
(MonadState(..), StateT(..), gets, MonadIO, modify')
import Control.Monad.ST (RealWorld)
import Control.Monad.Trans (lift)
import Data.Binary.Get (runGetOrFail)
import Data.ByteString.Lazy qualified as LBS
import Data.IORef (readIORef, atomicModifyIORef', writeIORef)
import Data.Foldable (foldlM)
import Data.IORef (readIORef, atomicModifyIORef', writeIORef)
import Data.List qualified as List
import Data.List.NonEmpty qualified as NEList
import Data.Map qualified as Map
import Data.Map (Map, (\\))
import Data.Map qualified as Map
import Data.Maybe (isJust, mapMaybe, fromJust)
import Data.Set (Set)
import Data.Set qualified as Set
@@ -32,8 +31,8 @@ import System.Random (mkStdGen)
import EVM (cheatCode)
import EVM.ABI (getAbi, AbiType(AbiAddressType, AbiTupleType), AbiValue(AbiAddress, AbiTuple), abiValueType)
import EVM.Dapp (DappInfo(..))
import EVM.Types hiding (Env, Frame(state), Gas)
import EVM.Solidity (SolcContract(..), Method(..))
import EVM.Types hiding (Env, Frame(state), Gas)
import Echidna.ABI
import Echidna.Events (extractEventValues)
@@ -42,15 +41,15 @@ import Echidna.Mutator.Corpus
import Echidna.Shrink (shrinkTest)
import Echidna.Solidity (chooseContract)
import Echidna.SymExec.Common (extractTxs, extractErrors)
import Echidna.SymExec.Symbolic (forceAddr)
import Echidna.SymExec.Exploration (exploreContract, getTargetMethodFromTx, getRandomTargetMethod)
import Echidna.SymExec.Symbolic (forceAddr)
import Echidna.SymExec.Verification (verifyMethod, isSuitableToVerifyMethod)
import Echidna.Test
import Echidna.Transaction
import Echidna.Types.Campaign
import Echidna.Types.Config
import Echidna.Types.Corpus (Corpus, corpusSize)
import Echidna.Types.Coverage (coverageStats)
import Echidna.Types.Config
import Echidna.Types.Random (rElem)
import Echidna.Types.Signature (FunctionName)
import Echidna.Types.Test
@@ -73,7 +72,7 @@ isSuccessful =
-- contain minimized corpus without sequences that didn't increase the coverage.
replayCorpus
:: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m, MonadState WorkerState m)
=> VM Concrete RealWorld -- ^ VM to start replaying from
=> VM Concrete -- ^ VM to start replaying from
-> [(FilePath, [Tx])] -- ^ corpus to replay
-> m ()
replayCorpus vm txSeqs =
@@ -93,7 +92,7 @@ runWorker
=> WorkerType
-> StateT WorkerState m ()
-- ^ Callback to run after each state update (for instrumentation)
-> VM Concrete RealWorld -- ^ Initial VM state
-> VM Concrete -- ^ Initial VM state
-> GenDict -- ^ Generation dictionary
-> Int -- ^ Worker id starting from 0
-> [(FilePath, [Tx])]
@@ -110,7 +109,7 @@ runSymWorker
:: (MonadIO m, MonadThrow m, MonadReader Env m)
=> StateT WorkerState m ()
-- ^ Callback to run after each state update (for instrumentation)
-> VM Concrete RealWorld -- ^ Initial VM state
-> VM Concrete -- ^ Initial VM state
-> GenDict -- ^ Generation dictionary
-> Int -- ^ Worker id starting from 0
-> [(FilePath, [Tx])]
@@ -324,7 +323,7 @@ runFuzzWorker
:: (MonadIO m, MonadThrow m, MonadReader Env m)
=> StateT WorkerState m ()
-- ^ Callback to run after each state update (for instrumentation)
-> VM Concrete RealWorld -- ^ Initial VM state
-> VM Concrete -- ^ Initial VM state
-> GenDict -- ^ Generation dictionary
-> Int -- ^ Worker id starting from 0
-> [(FilePath, [Tx])]
@@ -454,9 +453,9 @@ randseq deployedContracts = do
-- Returns resulting VM, as well as whether any new coverage was found.
callseq
:: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m, MonadState WorkerState m)
=> VM Concrete RealWorld
=> VM Concrete
-> [Tx]
-> m (VM Concrete RealWorld, Bool)
-> m (VM Concrete, Bool)
callseq vm txSeq = do
env <- ask
-- First, we figure out whether we need to execute with or without coverage
@@ -524,7 +523,7 @@ callseq vm txSeq = do
-- know the return type for each function called. If yes, try to parse the
-- return value as a value of that type. Returns a 'GenDict' style Map.
returnValues
:: [(Tx, VMResult Concrete RealWorld)]
:: [(Tx, VMResult Concrete)]
-> (FunctionName -> Maybe AbiType)
-> Map AbiType (Set AbiValue)
returnValues txResults returnTypeOf =
@@ -555,7 +554,7 @@ callseq vm txSeq = do
getTupleVector _ = error "Not a tuple!"
-- | Add transactions to the corpus, discarding reverted ones
addToCorpus :: Int -> [(Tx, VMResult Concrete RealWorld)] -> Corpus -> Corpus
addToCorpus :: Int -> [(Tx, VMResult Concrete)] -> Corpus -> Corpus
addToCorpus n res corpus =
if null rtxs then corpus else Set.insert (n, rtxs) corpus
where rtxs = fst <$> res
@@ -564,8 +563,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 Concrete RealWorld -> Tx
-> m (VMResult Concrete RealWorld, VM Concrete RealWorld)
=> VM Concrete -> Tx
-> m (VMResult Concrete, VM Concrete)
execTxOptC vm tx = do
((res, grew), vm') <- runStateT (execTxWithCov tx) vm
when grew $ do
@@ -581,10 +580,10 @@ execTxOptC vm tx = do
-- of transactions, constantly checking if we've solved any tests.
evalSeq
:: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m, MonadState WorkerState m)
=> VM Concrete RealWorld -- ^ Initial VM
-> (VM Concrete RealWorld -> Tx -> m (result, VM Concrete RealWorld))
=> VM Concrete -- ^ Initial VM
-> (VM Concrete -> Tx -> m (result, VM Concrete))
-> [Tx]
-> m ([(Tx, result)], VM Concrete RealWorld)
-> m ([(Tx, result)], VM Concrete)
evalSeq vm0 execFunc = go vm0 [] where
go vm executedSoFar toExecute = do
-- NOTE: we do reverse here because we build up this list by prepending,
@@ -627,7 +626,7 @@ findFailedTests = do
-- | Update an open test after checking if it is falsified by the 'reproducer'
updateOpenTest
:: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m, MonadState WorkerState m)
=> VM Concrete RealWorld -- ^ VM after applying potential reproducer
=> VM Concrete -- ^ VM after applying potential reproducer
-> [Tx] -- ^ potential reproducer
-> EchidnaTest
-> m (Maybe EchidnaTest)
+1 -1
View File
@@ -13,8 +13,8 @@ import Data.Set qualified as Set
import Data.Text (isPrefixOf)
import Data.Yaml qualified as Y
import EVM.Types (VM(..), W256)
import EVM.Solvers (Solver(..))
import EVM.Types (VM(..), W256)
import Echidna.Mutator.Corpus (defaultMutationConsts)
import Echidna.Test
+7 -8
View File
@@ -15,28 +15,27 @@ import Data.Text.Encoding (encodeUtf8)
import EVM.Solidity
import EVM.Types hiding (Env)
import Echidna.Exec (execTx, execTxWithCov)
import Echidna.Events (extractEvents)
import Echidna.Exec (execTx, execTxWithCov)
import Echidna.Types.Campaign (CampaignConf(..))
import Echidna.Types.Config (Env(..), EConfig(..))
import Echidna.Types.Solidity (SolException(..))
import Echidna.Types.Tx (createTx, unlimitedGasPerBlock)
import Control.Monad.ST (RealWorld)
deployContracts
:: (MonadIO m, MonadReader Env m, MonadThrow m)
=> [(Addr, SolcContract)]
-> Addr
-> VM Concrete RealWorld
-> m (VM Concrete RealWorld)
-> VM Concrete
-> m (VM Concrete)
deployContracts cs = deployBytecodes' $ map (\(a, c) -> (a, c.creationCode)) cs
deployBytecodes
:: (MonadIO m, MonadReader Env m, MonadThrow m)
=> [(Addr, Text)]
-> Addr
-> VM Concrete RealWorld
-> m (VM Concrete RealWorld)
-> VM Concrete
-> m (VM Concrete)
deployBytecodes cs = deployBytecodes' $
(\(a, bc) ->
(a, fromRight (error ("invalid b16 decoding of: " ++ show bc)) $ BS16.decode $ encodeUtf8 bc)
@@ -47,8 +46,8 @@ deployBytecodes'
:: (MonadIO m, MonadReader Env m, MonadThrow m)
=> [(Addr, ByteString)]
-> Addr
-> VM Concrete RealWorld
-> m (VM Concrete RealWorld)
-> VM Concrete
-> m (VM Concrete)
deployBytecodes' cs src initialVM = foldM deployOne initialVM cs
where
deployOne vm (dst, bytecode) = do
+3 -3
View File
@@ -35,7 +35,7 @@ type Events = [Text]
emptyEvents :: TreePos Empty a
emptyEvents = fromForest []
extractEvents :: Bool -> DappInfo -> VM Concrete s -> Events
extractEvents :: Bool -> DappInfo -> VM Concrete -> Events
extractEvents decodeErrors dappInfo vm =
let forest = traceForest vm
in maybeToList (decodeRevert decodeErrors vm)
@@ -78,7 +78,7 @@ extractEvents decodeErrors dappInfo vm =
_ -> Nothing
-- | Extract all non‑indexed event values emitted between two VM states.
extractEventValues :: DappInfo -> VM Concrete s -> VM Concrete s -> Map AbiType (Set AbiValue)
extractEventValues :: DappInfo -> VM Concrete -> VM Concrete -> Map AbiType (Set AbiValue)
extractEventValues dappInfo vm vm' =
let
oldLogs = vm.logs
@@ -113,7 +113,7 @@ maybeContractNameFromCodeHash info codeHash = contractToName <$> maybeContract
where maybeContract = snd <$> Map.lookup codeHash info.solcByHash
contractToName c = contractNamePart c.contractName
decodeRevert :: Bool -> VM Concrete s -> Maybe Text
decodeRevert :: Bool -> VM Concrete -> Maybe Text
decodeRevert decodeErrors vm =
case vm.result of
Just (VMFailure (Revert (ConcreteBuf bs))) -> decodeRevertMsg decodeErrors bs
+28 -33
View File
@@ -4,14 +4,11 @@
module Echidna.Exec where
import Optics.Core
import Optics.State.Operators
import Control.Monad (when, forM_)
import Control.Monad.Catch (MonadThrow(..))
import Control.Monad.State.Strict (MonadState(get, put), execState, runStateT, MonadIO(liftIO), gets, modify', execStateT)
import Control.Monad.Reader (MonadReader, ask, asks)
import Control.Monad.ST (ST, stToIO, RealWorld)
import Control.Monad.State.Strict (MonadState(get, put), execState, runStateT, MonadIO(liftIO), gets, modify', execStateT)
import Data.Bits
import Data.ByteString qualified as BS
import Data.IORef (readIORef, newIORef, writeIORef, modifyIORef')
@@ -20,6 +17,8 @@ import Data.Maybe (fromMaybe, fromJust)
import Data.Text qualified as T
import Data.Vector qualified as V
import Data.Vector.Unboxed.Mutable qualified as VMut
import Optics.Core
import Optics.State.Operators
import System.Environment (lookupEnv, getEnvironment)
import System.Process qualified as P
@@ -60,31 +59,31 @@ classifyError = \case
_ -> UnknownE
-- | Extracts the 'Query' if there is one.
getQuery :: VMResult Concrete s -> Maybe (Query Concrete s)
getQuery :: VMResult Concrete -> Maybe (Query Concrete)
getQuery (HandleEffect (Query q)) = Just q
getQuery _ = Nothing
-- | Matches execution errors that just cause a reversion.
pattern Reversion :: VMResult Concrete s
pattern Reversion :: VMResult Concrete
pattern Reversion <- VMFailure (classifyError -> RevertE)
-- | Matches execution errors caused by illegal behavior.
pattern Illegal :: VMResult Concrete s
pattern Illegal :: VMResult Concrete
pattern Illegal <- VMFailure (classifyError -> IllegalE)
-- | Given an execution error, throw the appropriate exception.
-- Also optionally takes a DappInfo and VM, which are used to show the stack trace.
vmExcept :: MonadThrow m => Maybe (DappInfo, VM Concrete RealWorld) -> EvmError -> m ()
vmExcept :: MonadThrow m => Maybe (DappInfo, VM Concrete) -> EvmError -> m ()
vmExcept traceInfo e =
let trace = uncurry showTraceTree <$> traceInfo
in throwM $
case VMFailure e of {Illegal -> IllegalExec e; _ -> UnknownFailure e trace}
execTxWith
:: (MonadIO m, MonadState (VM Concrete RealWorld) m, MonadReader Env m, MonadThrow m)
=> m (VMResult Concrete RealWorld)
:: (MonadIO m, MonadState (VM Concrete) m, MonadReader Env m, MonadThrow m)
=> m (VMResult Concrete)
-> Tx
-> m (VMResult Concrete RealWorld)
-> m (VMResult Concrete)
execTxWith executeTx tx = do
vm <- get
if hasSelfdestructed vm tx.dst then
@@ -120,16 +119,12 @@ execTxWith executeTx tx = do
session <- asks (.fetchSession)
ret <- liftIO $ safeFetchContractFrom session rpcBlock rpcUrl addr
case ret of
-- TODO: fix hevm to not return an empty contract in case of an error
Just contract | contract.code /= RuntimeCode (ConcreteRuntimeCode "") -> do
EVM.Fetch.FetchSuccess contract _ -> do
fromEVM (continuation contract)
_ -> do
-- TODO: better error reporting in HEVM, when intermittent
-- network error then retry
logMsg $ "ERROR: Failed to fetch contract: " <> show q
-- TODO: How should we fail here? It could be a network error,
-- RPC server returning junk etc.
EVM.Fetch.FetchFailure _ -> do
fromEVM (continuation emptyAccount)
EVM.Fetch.FetchError e -> do
error $ "ERROR: Failed to fetch contract: " <> show q <> " " <> T.unpack e
Nothing -> do
--logMsg $ "ERROR: Requested RPC but it is not configured: " <> show q
-- TODO: How should we fail here? RPC is not configured but VM
@@ -139,19 +134,19 @@ execTxWith executeTx tx = do
-- A previously unknown slot is required
Just q@(PleaseFetchSlot addr slot continuation) -> do
--logMsg $ "INFO: Performing RPC: " <> show q
case config.rpcUrl of
Just rpcUrl -> do
session <- asks (.fetchSession)
ret <- liftIO $ safeFetchSlotFrom session rpcBlock rpcUrl addr slot
case ret of
Just value -> do
EVM.Fetch.FetchSuccess value status -> do
-- Log only in text mode, ignoring quiet flag as this is important info
when (status == EVM.Fetch.Fresh) $ logMsg $ "Fetched new slot: " <> show q
fromEVM (continuation value)
Nothing -> do
-- TODO: How should we fail here? It could be a network error,
-- RPC server returning junk etc.
logMsg $ "ERROR: Failed to fetch slot: " <> show q
EVM.Fetch.FetchFailure _ -> do
fromEVM (continuation 0)
EVM.Fetch.FetchError e -> do
error $ "ERROR: Failed to fetch slot: " <> show q <> " " <> T.unpack e
Nothing -> do
--logMsg $ "ERROR: Requested RPC but it is not configured: " <> show q
-- Use the zero slot
@@ -217,9 +212,9 @@ logMsg msg = do
-- | Execute a transaction "as normal".
execTx
:: (MonadIO m, MonadReader Env m, MonadThrow m)
=> VM Concrete RealWorld
=> VM Concrete
-> Tx
-> m (VMResult Concrete RealWorld, VM Concrete RealWorld)
-> m (VMResult Concrete, VM Concrete)
execTx vm tx = runStateT (execTxWith (fromEVM (exec defaultConfig)) tx) vm
-- | A type alias for the context we carry while executing instructions
@@ -227,9 +222,9 @@ type CoverageContext = (Bool, Maybe (VMut.IOVector CoverageInfo, Int))
-- | Execute a transaction, logging coverage at every step.
execTxWithCov
:: (MonadIO m, MonadState (VM Concrete RealWorld) m, MonadReader Env m, MonadThrow m)
:: (MonadIO m, MonadState (VM Concrete) m, MonadReader Env m, MonadThrow m)
=> Tx
-> m (VMResult Concrete RealWorld, Bool)
-> m (VMResult Concrete, Bool)
execTxWithCov tx = do
env <- ask
@@ -260,7 +255,7 @@ execTxWithCov tx = do
pure r
where
-- | Repeatedly exec a step and add coverage until we have an end result
loop :: VM Concrete RealWorld -> IO (VMResult Concrete RealWorld, VM Concrete RealWorld)
loop :: VM Concrete -> IO (VMResult Concrete, VM Concrete)
loop !vm = case vm.result of
Nothing -> do
addCoverage vm
@@ -268,11 +263,11 @@ execTxWithCov tx = do
Just r -> pure (r, vm)
-- | Execute one instruction on the EVM
stepVM :: VM Concrete RealWorld -> IO (VM Concrete RealWorld)
stepVM :: VM Concrete -> IO (VM Concrete)
stepVM = stToIO . execStateT (exec1 defaultConfig)
-- | Add current location to the CoverageMap
addCoverage :: VM Concrete RealWorld -> IO ()
addCoverage :: VM Concrete -> IO ()
addCoverage !vm = do
let (pc, opIx, depth) = currentCovLoc vm
contract = currentContract vm
@@ -315,7 +310,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 Concrete s)
initialVM :: Bool -> ST RealWorld (VM Concrete)
initialVM ffi = do
vm <- vmForEthrunCreation mempty
pure $ vm & #block % #timestamp .~ Lit initialTimestamp
+1 -1
View File
@@ -6,8 +6,8 @@ import Data.Set qualified as Set
import Echidna.Mutator.Array
import Echidna.Transaction (mutateTx, shrinkTx)
import Echidna.Types (MutationConsts)
import Echidna.Types.Tx (Tx)
import Echidna.Types.Corpus
import Echidna.Types.Tx (Tx)
defaultMutationConsts :: Num a => MutationConsts a
defaultMutationConsts = (1, 1, 1, 1)
+19 -15
View File
@@ -2,25 +2,27 @@
module Echidna.Onchain where
import Control.Concurrent.MVar (readMVar)
import Control.Exception (catch)
import Control.Monad (when, forM_)
import Data.Aeson (ToJSON, FromJSON)
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.UTF8 qualified as UTF8
import Data.Map qualified as Map
import Data.Maybe (isJust, fromJust)
import Data.Text qualified as Text
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Vector qualified as Vector
import Data.Word (Word64)
import Etherscan qualified
import GHC.Generics (Generic)
import Network.HTTP.Simple (HttpException)
import Network.Wreq.Session qualified as Session
import Optics (view)
import System.Directory (doesFileExist)
import System.Environment (lookupEnv)
import System.FilePath ((</>))
import Network.Wreq.Session qualified as Session
import Text.Read (readMaybe)
import EVM (initialContract, bytecode)
@@ -29,13 +31,10 @@ import EVM.Fetch qualified
import EVM.Solidity (SourceCache(..), SolcContract (..))
import EVM.Types hiding (Env)
import Echidna.Output.Source (saveCoverages)
import Echidna.SymExec.Symbolic (forceWord, forceBuf)
import Echidna.Types (emptyAccount)
import Echidna.Types.Campaign (CampaignConf(..))
import Echidna.Types.Config (Env(..), EConfig(..))
import Echidna.Output.Source (saveCoverages)
import Control.Monad (when, forM_)
import Control.Concurrent.MVar (readMVar)
saveRpcCache :: Env -> IO ()
saveRpcCache env = do
@@ -62,19 +61,23 @@ etherscanApiKey = do
val <- lookupEnv "ETHERSCAN_API_KEY"
pure (Text.pack <$> val)
-- TODO: temporary solution, handle errors gracefully
safeFetchContractFrom :: EVM.Fetch.Session -> EVM.Fetch.BlockNumber -> Text -> Addr -> IO (Maybe Contract)
safeFetchContractFrom :: EVM.Fetch.Session -> EVM.Fetch.BlockNumber -> Text -> Addr -> IO (EVM.Fetch.FetchResult Contract)
safeFetchContractFrom session rpcBlock rpcUrl addr = do
catch
(EVM.Fetch.fetchContractWithSession defaultConfig session rpcBlock rpcUrl addr)
(\(_ :: HttpException) -> pure $ Just emptyAccount)
(do
res <- EVM.Fetch.fetchContractWithSession defaultConfig session rpcBlock rpcUrl addr
pure $ case res of
EVM.Fetch.FetchSuccess c status -> EVM.Fetch.FetchSuccess (EVM.Fetch.makeContractFromRPC c) status
EVM.Fetch.FetchFailure status -> EVM.Fetch.FetchFailure status
EVM.Fetch.FetchError e -> EVM.Fetch.FetchError e
)
(\(e :: HttpException) -> pure $ EVM.Fetch.FetchError (Text.pack $ show e))
-- TODO: temporary solution, handle errors gracefully
safeFetchSlotFrom :: EVM.Fetch.Session -> EVM.Fetch.BlockNumber -> Text -> Addr -> W256 -> IO (Maybe W256)
safeFetchSlotFrom :: EVM.Fetch.Session -> EVM.Fetch.BlockNumber -> Text -> Addr -> W256 -> IO (EVM.Fetch.FetchResult W256)
safeFetchSlotFrom session rpcBlock rpcUrl addr slot =
catch
(EVM.Fetch.fetchSlotWithCache defaultConfig session rpcBlock rpcUrl addr slot)
(\(_ :: HttpException) -> pure $ Just 0)
(\(e :: HttpException) -> pure $ EVM.Fetch.FetchError (Text.pack $ show e))
data FetchedContractData = FetchedContractData
{ runtimeCode :: ByteString
@@ -157,7 +160,7 @@ saveCoverageReport env runId = do
when (env.chainId == Just 1) $ do
-- Get contracts from hevm session cache
sessionCache <- readMVar env.fetchSession.sharedCache
let contractsCache = sessionCache.contractCache
let contractsCache = EVM.Fetch.makeContractFromRPC <$> sessionCache.contractCache
forM_ (Map.toList contractsCache) $ \(addr, contract) -> do
r <- externalSolcContract env addr contract
case r of
@@ -173,8 +176,9 @@ saveCoverageReport env runId = do
fetchChainIdFrom :: Maybe Text -> IO (Maybe W256)
fetchChainIdFrom (Just url) = do
sess <- Session.newAPISession
EVM.Fetch.fetchQuery
res <- EVM.Fetch.fetchQuery
EVM.Fetch.Latest -- this shouldn't matter
(EVM.Fetch.fetchWithSession url sess)
EVM.Fetch.QueryChainId
pure $ either (const Nothing) Just res
fetchChainIdFrom Nothing = pure Nothing
+3 -3
View File
@@ -5,17 +5,17 @@ module Echidna.Orphans.JSON () where
-- THIS MODULE EXPORTS NO FUNCTIONS
-- IT ONLY EXPORTS ORPHAN INSTANCES
import Prelude hiding (Word, fail)
import Control.Monad.Fail (fail)
import Data.Aeson (ToJSON(..), FromJSON(..), withText)
import Data.Aeson.TH (deriveJSON, defaultOptions)
import Data.ByteString (ByteString)
import Data.DoubleWord (Word256, Int256)
import Data.Text (Text, unpack)
import EVM.ABI (AbiValue, AbiType)
import Prelude hiding (Word, fail)
import Text.Read (readMaybe)
import EVM.ABI (AbiValue, AbiType)
readT :: Read a => Text -> Maybe a
readT = readMaybe . unpack
+3 -3
View File
@@ -11,13 +11,13 @@ import Data.Time (LocalTime)
import System.Directory (createDirectoryIfMissing, makeRelativeToCurrentDirectory, doesFileExist)
import System.FilePath ((</>), (<.>))
import Echidna.Types.Config
import Echidna.Types.Campaign
import Echidna.Types.Config
import Echidna.Types.Test (EchidnaTest(..))
import Echidna.Types.Worker
import Echidna.Types.Tx (Tx)
import Echidna.Worker (pushCampaignEvent)
import Echidna.Types.Worker
import Echidna.Utility (listDirectory, withCurrentDirectory)
import Echidna.Worker (pushCampaignEvent)
saveTxs :: Env -> FilePath -> [[Tx]] -> IO ()
saveTxs env dir = mapM_ saveTxSeq where
+4 -3
View File
@@ -9,15 +9,16 @@ import Data.List (elemIndex, nub)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Text (Text, unpack)
import Data.Text.Encoding (decodeUtf8)
import qualified Data.Text.Lazy as TL
import Data.Text.Lazy (fromStrict)
import Data.Vector as V hiding ((++), map, zipWith, elemIndex, mapMaybe)
import EVM.ABI (AbiValue(..))
import EVM.Types (W256, Addr)
import Numeric (showHex)
import qualified Data.Text.Lazy as TL
import Text.Mustache (Template, substituteValue, toMustache)
import Text.Mustache.Compile (embedTemplate)
import EVM.ABI (AbiValue(..))
import EVM.Types (W256, Addr)
import Echidna.Types.Test (EchidnaTest(..), TestType(..))
import Echidna.Types.Tx (Tx(..), TxCall(..))
+1 -1
View File
@@ -20,8 +20,8 @@ import Echidna.Events (Events, extractEvents)
import Echidna.Types.Campaign (WorkerState(..))
import Echidna.Types.Config (Env(..))
import Echidna.Types.Coverage (CoverageInfo, mergeCoverageMaps)
import Echidna.Types.Test qualified as T
import Echidna.Types.Test (EchidnaTest(..))
import Echidna.Types.Test qualified as T
import Echidna.Types.Tx (Tx(..), TxCall(..))
data Campaign = Campaign
+6 -7
View File
@@ -3,19 +3,14 @@
module Echidna.Output.Source where
import Prelude hiding (writeFile)
import Control.Monad (unless)
import Data.ByteString qualified as BS
import Text.Mustache (substituteValue, toMustache)
import Text.Mustache.Compile (embedTemplate)
import Text.Mustache.Types (Template, Value(..))
import Data.Foldable
import Data.List (nub, sort)
import Data.List.NonEmpty qualified as NE
import Data.Maybe (fromMaybe, mapMaybe, catMaybes)
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe (fromMaybe, mapMaybe, catMaybes)
import Data.Sequence qualified as Seq
import Data.Set qualified as S
import Data.Text (Text, pack)
@@ -26,19 +21,23 @@ import Data.Time (getCurrentTime, formatTime, defaultTimeLocale)
import Data.Vector qualified as V
import Data.Vector.Unboxed qualified as VU
import HTMLEntities.Text qualified as HTML
import Prelude hiding (writeFile)
import System.Directory (createDirectoryIfMissing)
import System.FilePath ((</>), splitDirectories, joinPath, takeDirectory)
import System.FilePath.Glob qualified as Glob
import Text.Mustache (substituteValue, toMustache)
import Text.Mustache.Compile (embedTemplate)
import Text.Mustache.Types (Template, Value(..))
import Text.Printf (printf)
import EVM.Dapp (srcMapCodePos, DappInfo(..))
import EVM.Solidity (SourceCache(..), SrcMap, SolcContract(..))
import Echidna.SourceAnalysis.Slither (AssertLocation(..), assertLocationList, SlitherInfo(..))
import Echidna.Types.Campaign (CampaignConf(..))
import Echidna.Types.Config (Env(..), EConfig(..))
import Echidna.Types.Coverage (OpIx, unpackTxResults, FrozenCoverageMap, CoverageFileType (..), mergeCoverageMaps)
import Echidna.Types.Tx (TxResult(..))
import Echidna.SourceAnalysis.Slither (AssertLocation(..), assertLocationList, SlitherInfo(..))
-- | Embedded template with partials for coverage reports
coverageTemplate :: Template
+1 -1
View File
@@ -10,9 +10,9 @@ import Data.Word (Word16)
import Network.Wai.EventSource (ServerEvent(..), eventSourceAppIO)
import Network.Wai.Handler.Warp (run)
import Echidna.Worker()
import Echidna.Types.Config (Env(..))
import Echidna.Types.Worker
import Echidna.Worker()
newtype SSE = SSE (LocalTime, CampaignEvent)
+10 -11
View File
@@ -5,26 +5,25 @@ import Control.Monad.Catch (MonadThrow)
import Control.Monad.Random.Strict (MonadRandom, getRandomR, uniform)
import Control.Monad.Reader.Class (MonadReader (ask), asks)
import Control.Monad.State.Strict (MonadIO)
import Control.Monad.ST (RealWorld)
import Data.Set qualified as Set
import Data.List qualified as List
import Data.Maybe (mapMaybe)
import Data.Set qualified as Set
import EVM.Types (VM, VMType(..))
import Echidna.Exec
import Echidna.Test (getResultFromVM, checkETest)
import Echidna.Transaction
import Echidna.Types.Campaign (CampaignConf(..))
import Echidna.Types.Config
import Echidna.Types.Solidity (SolConf(..))
import Echidna.Types.Test (TestValue(..), EchidnaTest(..), TestState(..), isOptimizationTest)
import Echidna.Types.Tx (Tx(..), hasReverted, isUselessNoCall, catNoCalls, TxCall(..))
import Echidna.Types.Config
import Echidna.Types.Campaign (CampaignConf(..))
import Echidna.Test (getResultFromVM, checkETest)
-- | Top level function to shrink the complexity of the sequence of transactions once
shrinkTest
:: (MonadIO m, MonadThrow m, MonadRandom m, MonadReader Env m)
=> VM Concrete RealWorld
=> VM Concrete
-> EchidnaTest
-> m (Maybe EchidnaTest)
shrinkTest vm test = do
@@ -71,14 +70,14 @@ removeUselessNoCalls = mapMaybe f
-- | Given a VM and a sequence of transactions, execute each transaction except the last one.
-- If a transaction reverts, replace it by a "NoCall" with the same parameters as the original call
-- (e.g. same block increment timestamp and number)
removeReverts :: (MonadIO m, MonadReader Env m, MonadThrow m) => VM Concrete RealWorld -> [Tx] -> m [Tx]
removeReverts :: (MonadIO m, MonadReader Env m, MonadThrow m) => VM Concrete -> [Tx] -> m [Tx]
removeReverts _ [] = return []
removeReverts vm txs = do
let (itxs, le) = (init txs, last txs)
ftxs <- removeReverts' vm itxs []
return (ftxs ++ [le])
removeReverts' :: (MonadIO m, MonadReader Env m, MonadThrow m) => VM Concrete RealWorld -> [Tx] -> [Tx] -> m [Tx]
removeReverts' :: (MonadIO m, MonadReader Env m, MonadThrow m) => VM Concrete -> [Tx] -> [Tx] -> m [Tx]
removeReverts' _ [] ftxs = return $ reverse ftxs
removeReverts' vm (t:txs) ftxs = do
(_, vm') <- execTx vm t
@@ -90,11 +89,11 @@ removeReverts' vm (t:txs) ftxs = do
-- generate a smaller one that still solves that test.
shrinkSeq
:: (MonadIO m, MonadRandom m, MonadReader Env m, MonadThrow m)
=> VM Concrete RealWorld
-> (VM Concrete RealWorld -> m (TestValue, VM Concrete RealWorld))
=> VM Concrete
-> (VM Concrete -> m (TestValue, VM Concrete))
-> TestValue
-> [Tx]
-> m (Maybe ([Tx], TestValue, VM Concrete RealWorld))
-> m (Maybe ([Tx], TestValue, VM Concrete))
shrinkSeq vm f v txs = do
-- apply one of the two possible simplification strategies (shrunk or shorten) with equal probability
txs' <- uniform =<< sequence [shorten, shrunk]
+5 -6
View File
@@ -1,12 +1,10 @@
module Echidna.Solidity where
import Optics.Core hiding (filtered)
import Control.Monad (when, unless, forM_)
import Control.Monad.Catch (MonadThrow(..))
import Control.Monad.Extra (whenM)
import Control.Monad.Reader (ReaderT(runReaderT))
import Control.Monad.ST (stToIO, RealWorld)
import Control.Monad.ST (stToIO)
import Control.Monad.State (runStateT)
import Data.Foldable (toList)
import Data.List (find, partition, isSuffixOf, (\\))
@@ -20,14 +18,15 @@ import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text (Text, isPrefixOf, isSuffixOf, append)
import Data.Text qualified as T
import Optics.Core hiding (filtered)
import System.Directory
(doesDirectoryExist, doesFileExist, findExecutable, listDirectory, removeFile)
import System.Process (StdStream(..), readCreateProcessWithExitCode, proc, std_err)
import System.Exit (ExitCode(..))
import System.FilePath (joinPath, splitDirectories, (</>))
import System.FilePath.Posix qualified as FPP
import System.IO (openFile, IOMode(..))
import System.Info (os)
import System.Process (StdStream(..), readCreateProcessWithExitCode, proc, std_err)
import EVM (initialContract, currentContract)
import EVM.ABI
@@ -115,7 +114,7 @@ staticAddresses SolConf{contractAddr, deployer, sender} =
Set.map AbiAddress $
Set.union sender (Set.fromList [contractAddr, deployer, 0x0])
populateAddresses :: Set Addr -> Integer -> VM Concrete s -> VM Concrete s
populateAddresses :: Set Addr -> Integer -> VM Concrete -> VM Concrete
populateAddresses addrs b vm =
Set.foldl' (\vm' addr ->
if deployed addr
@@ -169,7 +168,7 @@ loadSpecified
:: Env
-> SolcContract
-> [SolcContract]
-> IO (VM Concrete RealWorld)
-> IO (VM Concrete)
loadSpecified env mainContract cs = do
let solConf = env.cfg.solConf
+1 -1
View File
@@ -20,6 +20,7 @@ import Data.Set qualified as Set
import Data.Text (pack)
import System.Directory (findExecutable)
import System.Exit (ExitCode(..))
import System.IO (stderr, hPutStrLn)
import System.Process (StdStream(..), readCreateProcessWithExitCode, proc, std_err)
import Text.Read (readMaybe)
@@ -30,7 +31,6 @@ import Echidna.ABI (makeNumAbiValues, makeArrayAbiValues)
import Echidna.Types.Signature (ContractName, FunctionName)
import Echidna.Types.Solidity (SolConf(..))
import Echidna.Utility (measureIO)
import System.IO (stderr, hPutStrLn)
enhanceConstants :: SlitherInfo -> Set AbiValue
enhanceConstants si =
+4 -2
View File
@@ -5,16 +5,18 @@ import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.IORef (IORef, readIORef, atomicModifyIORef')
import Data.List (find)
import Data.Map.Strict qualified as Map
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Maybe (mapMaybe)
import Data.Vector qualified as V
import Echidna.SymExec.Symbolic (forceWord)
import EVM.Dapp (DappInfo(..), findSrc)
import EVM.Expr (maybeLitByteSimp)
import EVM.Solidity (SolcContract(..))
import EVM.Types (Contract(..), ContractCode(..), RuntimeCode(..), W256)
import Echidna.SymExec.Symbolic (forceWord)
-- | Map from contracts' codehashes to their compile-time codehash.
-- This is relevant when the immutables solidity feature is used;
-- when this feature is not used, the map will just end up being an identity map.
+15 -15
View File
@@ -1,6 +1,7 @@
module Echidna.SymExec.Common where
import Control.Monad.IO.Unlift (MonadUnliftIO, liftIO)
import Control.Monad.State.Strict (execState, runStateT)
import Data.Function ((&))
import Data.Map qualified as Map
import Data.Maybe (fromMaybe, mapMaybe)
@@ -8,19 +9,18 @@ import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text qualified as T
import Optics.Core ((.~), (%), (%~))
import EVM.ABI (abiKind, AbiKind(Dynamic), Sig(..), decodeBuf, AbiVals(..))
import EVM.Fetch qualified as Fetch
import EVM (loadContract, resetState, symbolify)
import EVM.ABI (abiKind, AbiKind(Dynamic), Sig(..), decodeBuf, AbiVals(..))
import EVM.Effects (TTY, ReadConfig)
import EVM.Expr qualified
import EVM.Fetch qualified as Fetch
import EVM.Format (formatPartialDetailed)
import EVM.Solidity (SolcContract(..), SourceCache(..), Method(..), WarningData(..))
import EVM.Solvers (SolverGroup)
import EVM.SymExec (mkCalldata, verifyInputs, VeriOpts(..), checkAssertions, subModel, defaultSymbolicValues)
import EVM.Expr qualified
import EVM.Types (Addr, VMType(..), EType(..), Expr(..), Block(..), W256, SMTCex(..), ProofResult(..), Prop(..), forceLit)
import EVM.SymExec (mkCalldata, verifyInputsWithHandler, VeriOpts(..), checkAssertions, subModel, defaultSymbolicValues)
import EVM.Types (Addr, VMType(..), EType(..), Expr(..), Block(..), W256, SMTCex(..), ProofResult(..), Prop(..), forceLit, isQed)
import qualified EVM.Types (VM(..))
import EVM.Format (formatPartialDetailed)
import Control.Monad.ST (RealWorld)
import Control.Monad.State.Strict (execState, runStateT)
import Echidna.Types (fromEVM)
import Echidna.Types.Config (EConfig(..))
@@ -43,8 +43,8 @@ extractErrors = mapMaybe (\case
_ -> Nothing)
suitableForSymExec :: Method -> Bool
suitableForSymExec m = not $ null m.inputs
&& null (filter (\(_, t) -> abiKind t == Dynamic) m.inputs)
suitableForSymExec m = not $ null m.inputs
&& null (filter (\(_, t) -> abiKind t == Dynamic) m.inputs)
&& not (T.isInfixOf "_no_symexec" m.name)
@@ -125,8 +125,8 @@ getUnknownLogs = mapMaybe (\case
_ -> Nothing)
exploreMethod :: (MonadUnliftIO m, ReadConfig m, TTY m) =>
Method -> SolcContract -> SourceCache -> EVM.Types.VM Concrete RealWorld -> Addr -> EConfig -> VeriOpts -> SolverGroup -> Fetch.RpcInfo -> Fetch.Session -> m ([TxOrError], PartialsLogs)
Method -> SolcContract -> SourceCache -> EVM.Types.VM Concrete -> Addr -> EConfig -> VeriOpts -> SolverGroup -> Fetch.RpcInfo -> Fetch.Session -> m ([TxOrError], PartialsLogs)
exploreMethod method contract sources vm defaultSender conf veriOpts solvers rpcInfo session = do
calldataSym@(_, constraints) <- mkCalldata (Just (Sig method.methodSignature (snd <$> method.inputs))) []
let
@@ -148,8 +148,8 @@ exploreMethod method contract sources vm defaultSender conf veriOpts solvers rpc
-- TODO we might want to switch vm's state.baseState value to to AbstractBase eventually.
-- Doing so might mess up concolic execution.
(_, models, partials) <- verifyInputs solvers veriOpts fetcher vm'' (Just $ checkAssertions [0x1])
let results = map fst models
let warnData = Just $ WarningData contract sources vm'
(models, partials) <- verifyInputsWithHandler solvers veriOpts fetcher vm'' (checkAssertions [0x1]) Nothing
let results = filter (\(r, _) -> not (isQed r)) models & map fst
let warnData = Just $ WarningData contract sources vm'
--liftIO $ mapM_ TIO.putStrLn partials
return (map (modelToTx dst vm.block.timestamp vm.block.number method conf.solConf.sender defaultSender cd) results, map (formatPartialDetailed warnData . fst) partials)
+8 -8
View File
@@ -9,29 +9,29 @@ import Control.Monad.Catch (MonadThrow(..))
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Reader (MonadReader, ask, asks, runReaderT, liftIO)
import Control.Monad.State.Strict (MonadState)
import Data.List.NonEmpty (fromList)
import Data.Map qualified as Map
import Data.Maybe (fromJust)
import Data.Set qualified as Set
import Data.Text (unpack, Text)
import Data.List.NonEmpty (fromList)
import EVM.Dapp (DappInfo(..))
import EVM.Effects (defaultEnv, defaultConfig, Config(..), Env(..))
import EVM.Fetch (RpcInfo(..))
import EVM.Solidity (SolcContract(..), Method(..))
import EVM.Solvers (withSolvers)
import EVM.Dapp (DappInfo(..))
import EVM.SymExec (IterConfig(..), LoopHeuristic (..), VeriOpts(..))
import EVM.Fetch (RpcInfo(..))
import EVM.Types (VMType(..))
import qualified EVM.Types (VM(..))
import Control.Monad.ST (RealWorld)
import Echidna.SymExec.Common (suitableForSymExec, exploreMethod, rpcFetcher, TxOrError(..), PartialsLogs)
import Echidna.Types.Campaign (CampaignConf(..), WorkerState)
import Echidna.Types.Config (Env(..), EConfig(..), OperationMode(..), OutputFormat(..), UIConf(..))
import Echidna.Types.World (World(..))
import Echidna.Types.Random (rElem)
import Echidna.Types.Solidity (SolConf(..))
import Echidna.Types.Tx (Tx(..), TxCall(..))
import Echidna.Types.Worker (WorkerEvent(..))
import Echidna.Types.Random (rElem)
import Echidna.SymExec.Common (suitableForSymExec, exploreMethod, rpcFetcher, TxOrError(..), PartialsLogs)
import Echidna.Types.World (World(..))
import Echidna.Worker (pushWorkerEvent)
-- | Uses symbolic execution to find transactions which would increase coverage.
@@ -81,7 +81,7 @@ getRandomTargetMethod contract targets failedProperties = do
[] -> return Nothing
_ -> liftIO $ rElem (fromList $ map (Just . snd) targetMethods)
exploreContract :: (MonadIO m, MonadThrow m, MonadReader Echidna.Types.Config.Env m, MonadState WorkerState m) => SolcContract -> Method -> EVM.Types.VM Concrete RealWorld -> m (ThreadId, MVar ([TxOrError], PartialsLogs))
exploreContract :: (MonadIO m, MonadThrow m, MonadReader Echidna.Types.Config.Env m, MonadState WorkerState m) => SolcContract -> Method -> EVM.Types.VM Concrete -> m (ThreadId, MVar ([TxOrError], PartialsLogs))
exploreContract contract method vm = do
conf <- asks (.cfg)
dappInfo <- asks (.dapp)
+1
View File
@@ -4,6 +4,7 @@
module Echidna.SymExec.Symbolic where
import Data.ByteString (ByteString)
import EVM.Types (Expr(..), EType(..), W256, Addr)
forceBuf :: Expr Buf -> ByteString
+8 -8
View File
@@ -7,27 +7,27 @@ import Control.Monad.Catch (MonadThrow(..))
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Reader (MonadReader, ask, asks, runReaderT, liftIO)
import Control.Monad.State.Strict (MonadState)
import Data.List (find)
import Data.Map (assocs)
import Data.Maybe (fromJust)
import Data.Text qualified as Text
import Data.Set qualified as Set
import Data.List (find)
import Data.Text qualified as Text
import EVM.Dapp (DappInfo(..))
import EVM.Effects (defaultEnv, defaultConfig, Config(..), Env(..))
import EVM.Fetch (RpcInfo(..))
import EVM.Solidity (SolcContract(..), Method(..))
import EVM.Solvers (withSolvers)
import EVM.Dapp (DappInfo(..))
import EVM.SymExec (IterConfig(..), LoopHeuristic (..), VeriOpts(..))
import EVM.Types (VMType(..))
import EVM.Fetch (RpcInfo(..))
import qualified EVM.Types (VM(..))
import Control.Monad.ST (RealWorld)
import Echidna.SymExec.Common (rpcFetcher, exploreMethod, suitableForSymExec, TxOrError(..), PartialsLogs)
import Echidna.Types.Campaign (CampaignConf(..), WorkerState)
import Echidna.Types.Config (Env(..), EConfig(..), OperationMode(..), OutputFormat(..), UIConf(..))
import Echidna.Types.World (World(..))
import Echidna.Types.Solidity (SolConf(..))
import Echidna.Types.Worker (WorkerEvent(..))
import Echidna.SymExec.Common (rpcFetcher, exploreMethod, suitableForSymExec, TxOrError(..), PartialsLogs)
import Echidna.Types.World (World(..))
import Echidna.Worker (pushWorkerEvent)
isSuitableToVerifyMethod :: (MonadIO m, MonadReader Echidna.Types.Config.Env m) => SolcContract -> Method -> [Text.Text] -> m Bool
@@ -45,7 +45,7 @@ isSuitableToVerifyMethod contract method symExecTargets = do
else method.name `elem` symExecTargets
verifyMethod :: (MonadIO m, MonadThrow m, MonadReader Echidna.Types.Config.Env m, MonadState WorkerState m) => Method -> SolcContract -> EVM.Types.VM Concrete RealWorld -> m (ThreadId, MVar ([TxOrError], PartialsLogs))
verifyMethod :: (MonadIO m, MonadThrow m, MonadReader Echidna.Types.Config.Env m, MonadState WorkerState m) => Method -> SolcContract -> EVM.Types.VM Concrete -> m (ThreadId, MVar ([TxOrError], PartialsLogs))
verifyMethod method contract vm = do
conf <- asks (.cfg)
dappInfo <- asks (.dapp)
+26 -28
View File
@@ -2,16 +2,14 @@
module Echidna.Test where
import Prelude hiding (Word)
import Control.Monad.Catch (MonadThrow)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Reader.Class (MonadReader, asks)
import Control.Monad.ST (RealWorld)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Data.Text (Text)
import Data.Text qualified as T
import Prelude hiding (Word)
import EVM.ABI (AbiValue(..), AbiType(..), encodeAbiValue, decodeAbiValue)
import EVM.Dapp (DappInfo)
@@ -31,7 +29,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 Concrete s -> CallRes
classifyRes :: VMResult Concrete -> CallRes
classifyRes (VMSuccess b)
| forceBuf b == encodeAbiValue (AbiBool True) = ResTrue
| forceBuf b == encodeAbiValue (AbiBool False) = ResFalse
@@ -39,7 +37,7 @@ classifyRes (VMSuccess b)
classifyRes Reversion = ResRevert
classifyRes _ = ResOther
getResultFromVM :: VM Concrete s -> TxResult
getResultFromVM :: VM Concrete -> TxResult
getResultFromVM vm =
case vm.result of
Just r -> getResult r
@@ -114,7 +112,7 @@ createTests m td ts r ss = case m of
updateOpenTest
:: EchidnaTest
-> [Tx]
-> (TestValue, VM Concrete RealWorld, TxResult)
-> (TestValue, VM Concrete, TxResult)
-> EchidnaTest
updateOpenTest test txs (BoolValue False, vm, r) =
test { Test.state = Large 0, reproducer = txs, vm = Just vm, result = r }
@@ -139,8 +137,8 @@ updateOpenTest _ _ _ = error "Invalid type of test"
checkETest
:: (MonadIO m, MonadReader Env m, MonadThrow m)
=> EchidnaTest
-> VM Concrete RealWorld
-> m (TestValue, VM Concrete RealWorld)
-> VM Concrete
-> m (TestValue, VM Concrete)
checkETest test vm = case test.testType of
Exploration -> pure (BoolValue True, vm) -- These values are never used
PropertyTest n a -> checkProperty vm n a
@@ -152,10 +150,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 Concrete RealWorld
=> VM Concrete
-> Text
-> Addr
-> m (TestValue, VM Concrete RealWorld)
-> m (TestValue, VM Concrete)
checkProperty vm f a = do
case vm.result of
Just (VMSuccess _) -> do
@@ -166,11 +164,11 @@ checkProperty vm f a = do
runTx
:: (MonadIO m, MonadReader Env m, MonadThrow m)
=> VM Concrete RealWorld
=> VM Concrete
-> Text
-> (Addr -> Addr)
-> Addr
-> m (VM Concrete RealWorld)
-> m (VM Concrete)
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)
@@ -178,7 +176,7 @@ runTx vm f s a = do
pure vm'
--- | Extract a test value from an execution.
getIntFromResult :: Maybe (VMResult Concrete RealWorld) -> TestValue
getIntFromResult :: Maybe (VMResult Concrete) -> TestValue
getIntFromResult (Just (VMSuccess b)) =
let bs = forceBuf b
in case decodeAbiValue (AbiIntType 256) $ LBS.fromStrict bs of
@@ -189,10 +187,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 Concrete RealWorld
=> VM Concrete
-> Text
-> Addr
-> m (TestValue, VM Concrete RealWorld)
-> m (TestValue, VM Concrete)
checkOptimization vm f a = do
TestConf _ s <- asks (.cfg.testConf)
vm' <- runTx vm f s a
@@ -200,10 +198,10 @@ checkOptimization vm f a = do
checkStatefulAssertion
:: (MonadReader Env m, MonadThrow m)
=> VM Concrete RealWorld
=> VM Concrete
-> SolSignature
-> Addr
-> m (TestValue, VM Concrete RealWorld)
-> m (TestValue, VM Concrete)
checkStatefulAssertion vm sig addr = do
dappInfo <- asks (.dapp)
let
@@ -230,10 +228,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 Concrete RealWorld
=> VM Concrete
-> SolSignature
-> Addr
-> m (TestValue, VM Concrete RealWorld)
-> m (TestValue, VM Concrete)
checkDapptestAssertion vm sig addr = do
let
-- Whether the last transaction has any value
@@ -254,14 +252,14 @@ checkDapptestAssertion vm sig addr = do
checkCall
:: (MonadReader Env m, MonadThrow m)
=> VM Concrete RealWorld
-> (DappInfo -> VM Concrete RealWorld -> TestValue)
-> m (TestValue, VM Concrete RealWorld)
=> VM Concrete
-> (DappInfo -> VM Concrete -> TestValue)
-> m (TestValue, VM Concrete)
checkCall vm f = do
dappInfo <- asks (.dapp)
pure (f dappInfo vm, vm)
checkAssertionTest :: DappInfo -> VM Concrete RealWorld -> TestValue
checkAssertionTest :: DappInfo -> VM Concrete -> TestValue
checkAssertionTest dappInfo vm =
let events = extractEvents False dappInfo vm
in BoolValue $ null events || not (checkAssertionEvent events)
@@ -269,19 +267,19 @@ checkAssertionTest dappInfo vm =
checkAssertionEvent :: Events -> Bool
checkAssertionEvent = any (T.isPrefixOf "AssertionFailed(")
checkSelfDestructedTarget :: Addr -> DappInfo -> VM Concrete RealWorld -> TestValue
checkSelfDestructedTarget :: Addr -> DappInfo -> VM Concrete -> TestValue
checkSelfDestructedTarget addr _ vm =
let selfdestructs' = vm.tx.subState.selfdestructs
in BoolValue $ LitAddr addr `notElem` selfdestructs'
checkAnySelfDestructed :: DappInfo -> VM Concrete RealWorld -> TestValue
checkAnySelfDestructed :: DappInfo -> VM Concrete -> TestValue
checkAnySelfDestructed _ vm =
BoolValue $ null vm.tx.subState.selfdestructs
checkPanicEvent :: T.Text -> Events -> Bool
checkPanicEvent n = any (T.isPrefixOf ("Panic(" <> n <> ")"))
checkOverflowTest :: DappInfo -> VM Concrete RealWorld-> TestValue
checkOverflowTest :: DappInfo -> VM Concrete-> TestValue
checkOverflowTest dappInfo vm =
let es = extractEvents False dappInfo vm
in BoolValue $ null es || not (checkPanicEvent "17" es)
@@ -289,9 +287,9 @@ checkOverflowTest dappInfo vm =
-- | Reproduce a test saving VM snapshot after every transaction
reproduceTest
:: (MonadIO m, MonadThrow m, MonadReader Env m)
=> VM Concrete RealWorld -- ^ Initial VM
=> VM Concrete -- ^ Initial VM
-> EchidnaTest
-> m ([(Tx, VM Concrete RealWorld)], VM Concrete RealWorld)
-> m ([(Tx, VM Concrete)], VM Concrete)
reproduceTest vm0 test = do
let txs = test.reproducer
(results, vm) <- go vm0 [] txs
+4 -6
View File
@@ -3,21 +3,19 @@
module Echidna.Transaction where
import Optics.Core
import Optics.State.Operators
import Control.Monad (join, when)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Random.Strict (MonadRandom, getRandomR, uniform)
import Control.Monad.Reader (MonadReader, ask)
import Control.Monad.State.Strict (MonadState, gets, modify', execState)
import Control.Monad.ST (RealWorld)
import Data.ByteString qualified as BS
import Data.Map (Map, toList)
import Data.Maybe (catMaybes)
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Vector qualified as V
import Optics.Core
import Optics.State.Operators
import EVM (ceilDiv, initialContract, loadContract, resetState)
import EVM.ABI (abiValueType)
@@ -38,7 +36,7 @@ import Echidna.Types.Tx
import Echidna.Types.World (World(..))
import Echidna.Types.Campaign
hasSelfdestructed :: VM Concrete s -> Addr -> Bool
hasSelfdestructed :: VM Concrete -> 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
@@ -160,7 +158,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 Concrete RealWorld) m) => Tx -> m ()
setupTx :: (MonadIO m, MonadState (VM Concrete) m) => Tx -> m ()
setupTx tx@Tx{call = NoCall} = fromEVM $ do
resetState
modify' $ \vm -> vm
+3 -2
View File
@@ -1,10 +1,11 @@
module Echidna.Types where
import Control.Exception (Exception)
import Control.Monad.ST (stToIO)
import Control.Monad.State.Strict (MonadState, get, put, MonadIO(liftIO), runStateT)
import Control.Monad.ST (RealWorld, stToIO)
import Data.Text (Text, unpack)
import Data.Word (Word64)
import EVM (initialContract)
import EVM.Types
@@ -34,7 +35,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 Concrete RealWorld) m) => EVM Concrete RealWorld r -> m r
fromEVM :: (MonadIO m, MonadState (VM Concrete) m) => EVM Concrete r -> m r
fromEVM evmAction = do
vm <- get
(result, vm') <- liftIO $ stToIO $ runStateT evmAction vm
+1
View File
@@ -2,6 +2,7 @@ module Echidna.Types.Cache where
import Data.Map (Map)
import Data.Text (Text)
import EVM.Types (W256, Addr, Contract)
type ContractCache = Map Addr (Maybe Contract)
+3 -3
View File
@@ -9,19 +9,19 @@ import Data.Time (LocalTime)
import Data.Word (Word64)
import EVM.Dapp (DappInfo)
import EVM.Types (Addr, W256)
import EVM.Fetch qualified as Fetch
import EVM.Types (Addr, W256)
import Echidna.SourceAnalysis.Slither (SlitherInfo)
import Echidna.SourceMapping (CodehashMap)
import Echidna.Types.Cache
import Echidna.Types.Campaign (CampaignConf)
import Echidna.Types.Worker (CampaignEvent)
import Echidna.Types.Corpus (Corpus)
import Echidna.Types.Coverage (CoverageMap)
import Echidna.Types.Solidity (SolConf)
import Echidna.Types.Test (TestConf, EchidnaTest)
import Echidna.Types.Tx (TxConf)
import Echidna.Types.Cache
import Echidna.Types.Worker (CampaignEvent)
import Echidna.Types.World (World)
data OperationMode = Interactive | NonInteractive OutputFormat deriving (Show, Eq)
+1
View File
@@ -1,6 +1,7 @@
module Echidna.Types.Corpus where
import Data.Set (Set, size)
import Echidna.Types.Tx (Tx)
type Corpus = Set (Int, [Tx])
+2 -1
View File
@@ -9,10 +9,11 @@ import Data.Map qualified as Map
import Data.Map.Strict (Map)
import Data.Set qualified as Set
import Data.Text (toLower)
import Data.Vector.Unboxed qualified as V
import Data.Vector.Unboxed.Mutable (IOVector)
import Data.Vector.Unboxed.Mutable qualified as VM
import Data.Vector.Unboxed qualified as V
import Data.Word (Word64)
import EVM.Dapp (DappInfo(..))
import EVM.Solidity (SolcContract(..))
import EVM.Types (W256)
+1 -1
View File
@@ -1,11 +1,11 @@
module Echidna.Types.Random where
import Prelude hiding ((!!))
import Control.Monad.Random.Strict (MonadRandom, RandT, StdGen, getRandomR, weighted, evalRandT, getStdGen, forM_, liftIO)
import Data.Array.IO (IOArray, newListArray, readArray, writeArray, getElems)
import Data.List.NonEmpty ((!!), NonEmpty(..))
import Data.Set (Set)
import Data.Set qualified as S
import Prelude hiding ((!!))
type Seed = Int
+1 -1
View File
@@ -5,11 +5,11 @@ module Echidna.Types.Signature where
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.List.NonEmpty (NonEmpty)
import Data.Map (Map)
import Data.Text (Text)
import EVM.ABI (AbiType, AbiValue)
import EVM.Types (Addr, W256)
import Data.Map (Map)
-- | Name of the contract
type ContractName = Text
+4 -5
View File
@@ -3,7 +3,6 @@
module Echidna.Types.Test where
import Control.Monad.ST (RealWorld)
import Data.Aeson
import Data.DoubleWord (Int256)
import Data.Maybe (maybeToList)
@@ -13,17 +12,17 @@ import GHC.Generics (Generic)
import EVM.Dapp (DappInfo)
import EVM.Types (Addr, VM, VMType(Concrete))
import Echidna.ABI (encodeSig)
import Echidna.Types (ExecException)
import Echidna.Types.Signature (SolSignature)
import Echidna.Types.Tx (Tx, TxResult)
import Echidna.ABI (encodeSig)
-- | Test mode is parsed from a string
type TestMode = String
-- | Configuration for the creation of Echidna tests.
data TestConf = TestConf
{ classifier :: Text -> VM Concrete RealWorld -> Bool
{ classifier :: Text -> VM Concrete -> Bool
-- ^ Given a VM state and test name, check if a test just passed (typically
-- examining '_result'.)
, testSender :: Addr -> Addr
@@ -57,7 +56,7 @@ data TestType
= PropertyTest Text Addr
| OptimizationTest Text Addr
| AssertionTest Bool SolSignature Addr
| CallTest Text (DappInfo -> VM Concrete RealWorld -> TestValue)
| CallTest Text (DappInfo -> VM Concrete -> TestValue)
| Exploration
instance Eq TestType where
@@ -104,7 +103,7 @@ data EchidnaTest = EchidnaTest
, value :: TestValue
, reproducer :: [Tx]
, result :: TxResult
, vm :: Maybe (VM Concrete RealWorld)
, vm :: Maybe (VM Concrete)
-- | Worker which falsified the test will also shrink it.
, workerId :: Maybe Int
} deriving (Show)
+6 -8
View File
@@ -6,18 +6,19 @@
module Echidna.Types.Tx where
import Prelude hiding (Word)
import Control.Applicative ((<|>))
import Control.Monad.ST (RealWorld)
import Control.DeepSeq (NFData)
import Data.Aeson (FromJSON, ToJSON, parseJSON, toJSON, object, withObject, (.=), (.:))
import Data.Aeson.TH (deriveJSON, defaultOptions)
import Data.Aeson.Types (Parser)
import Data.ByteString (ByteString)
import Data.DoubleWord (Word256, Word128, Int256, Int128, Word160)
import Data.Hashable (Hashable(..))
import Data.Text (Text)
import Data.Vector (Vector, toList)
import Data.Word (Word64)
import GHC.Generics (Generic)
import Prelude hiding (Word)
import EVM.ABI (encodeAbiValue, AbiValue(..), AbiType)
import EVM.Types
@@ -25,9 +26,6 @@ import EVM.Types
import Echidna.Orphans.JSON ()
import Echidna.SymExec.Symbolic (forceBuf)
import Echidna.Types.Signature (SolCall)
import Control.DeepSeq (NFData)
import GHC.Generics (Generic)
import Data.DoubleWord (Word256, Word128, Int256, Int128, Word160)
-- | A transaction call is either a @CREATE@, a fully instrumented 'SolCall', or
-- an abstract call consisting only of calldata.
@@ -213,7 +211,7 @@ data TxConf = TxConf
-- ^ Maximum value to use in transactions
}
hasReverted :: VM Concrete RealWorld -> Bool
hasReverted :: VM Concrete -> Bool
hasReverted vm = let r = vm.result in
case r of
(Just (VMSuccess _)) -> False
@@ -232,7 +230,7 @@ catNoCalls (tx1:tx2:xs) =
where nc = tx1 { delay = (fst tx1.delay + fst tx2.delay, snd tx1.delay + snd tx2.delay) }
-- | Transform a VMResult into a more hash-friendly sum type
getResult :: VMResult Concrete s -> TxResult
getResult :: VMResult Concrete -> TxResult
getResult = \case
VMSuccess b | forceBuf b == encodeAbiValue (AbiBool True) -> ReturnTrue
| forceBuf b == encodeAbiValue (AbiBool False) -> ReturnFalse
+1 -1
View File
@@ -4,8 +4,8 @@ import Data.Set (Set)
import EVM.Types (Addr, FunctionSelector)
import Echidna.Types.Signature (SignatureMap)
import Echidna.Events (EventMap)
import Echidna.Types.Signature (SignatureMap)
-- | The world is composed by:
-- * A list of "human" addresses
+5 -8
View File
@@ -12,7 +12,6 @@ import Control.Monad
import Control.Monad.Catch
import Control.Monad.Reader
import Control.Monad.State.Strict hiding (state)
import Control.Monad.ST (RealWorld)
import Data.ByteString.Lazy qualified as BS
import Data.List.Split (chunksOf)
import Data.Map (Map)
@@ -20,16 +19,17 @@ import Data.Maybe (isJust, mapMaybe)
import Data.Sequence ((|>))
import Data.Text (Text)
import Data.Time
import Graphics.Vty qualified as Vty
import Graphics.Vty.Config (VtyUserConfig, defaultConfig, configInputMap)
import Graphics.Vty.CrossPlatform (mkVty)
import Graphics.Vty.Input.Events
import Graphics.Vty qualified as Vty
import System.Console.ANSI (hNowSupportsANSI)
import System.Signal
import UnliftIO
( MonadUnliftIO, IORef, newIORef, readIORef, hFlush, stdout , writeIORef, timeout)
import UnliftIO.Concurrent hiding (killThread, threadDelay)
import EVM.Fetch qualified
import EVM.Types (Addr, Contract, VM, VMType(Concrete), W256)
import Echidna.ABI
@@ -44,7 +44,7 @@ import Echidna.Types.Corpus qualified as Corpus
import Echidna.Types.Coverage (coverageStats)
import Echidna.Types.Test (EchidnaTest(..), TestState(..), didFail, isOptimizationTest)
import Echidna.Types.Tx (Tx)
import Echidna.Types.Worker
import Echidna.Types.Worker
import Echidna.UI.Report
import Echidna.UI.Widgets
import Echidna.Utility (timePrefix, getTimestamp)
@@ -66,7 +66,7 @@ data GasTracker = GasTracker
-- print non-interactive output in desired format at the end
ui
:: (MonadCatch m, MonadReader Env m, MonadUnliftIO m)
=> VM Concrete RealWorld -- ^ Initial VM state
=> VM Concrete -- ^ Initial VM state
-> GenDict
-> [(FilePath, [Tx])]
-> Maybe Text
@@ -114,9 +114,7 @@ ui vm dict initialCorpus cliSelectedContract = do
writeBChan uiChannel (CampaignUpdated now tests states)
-- TODO: remove and use events for this
-- For now, return empty cache data since accessing hevm's internal cache is complex
let c = mempty :: Map Addr (Maybe Contract)
let s = mempty :: Map Addr (Map W256 (Maybe W256))
(c, s) <- EVM.Fetch.getCacheState env.fetchSession
writeBChan uiChannel (FetchCacheUpdated c s)
-- UI initialization
@@ -429,4 +427,3 @@ statusLine env states lastUpdateRef = do
<> ", corpus: " <> show (Corpus.corpusSize corpus)
<> shrinkingPart
<> ", gas/s: " <> show gasPerSecond
+13 -14
View File
@@ -2,7 +2,6 @@ module Echidna.UI.Report where
import Control.Monad (forM)
import Control.Monad.Reader (MonadReader, MonadIO (liftIO), asks, ask)
import Control.Monad.ST (RealWorld)
import Data.IORef (readIORef, atomicModifyIORef')
import Data.List (nub)
import Data.Map qualified as Map
@@ -12,6 +11,10 @@ import Data.Text qualified as T
import Data.Time (LocalTime)
import Optics
import EVM.Format (showTraceTree, contractNamePart)
import EVM.Solidity (SolcContract(..))
import EVM.Types (W256, VM(labels), VMType(Concrete), Addr, Expr (LitAddr), Contract(..))
import Echidna.ABI (GenDict(..), encodeSig)
import Echidna.Pretty (ppTxCall)
import Echidna.SourceMapping (findSrcByMetadata, lookupCodehash)
@@ -26,11 +29,7 @@ import Echidna.Types.Worker
import Echidna.Utility (timePrefix)
import Echidna.Worker
import EVM.Format (showTraceTree, contractNamePart)
import EVM.Solidity (SolcContract(..))
import EVM.Types (W256, VM(labels), VMType(Concrete), Addr, Expr (LitAddr), Contract(..))
ppLogLine :: (MonadReader Env m, MonadIO m) => VM Concrete RealWorld -> (LocalTime, CampaignEvent) -> m String
ppLogLine :: (MonadReader Env m, MonadIO m) => VM Concrete -> (LocalTime, CampaignEvent) -> m String
ppLogLine vm (time, event@(WorkerEvent workerId FuzzWorker _)) =
((timePrefix time <> "[Worker " <> show workerId <> "] ") <>) <$> ppCampaignEventLog vm event
ppLogLine vm (time, event@(WorkerEvent workerId SymbolicWorker _)) =
@@ -38,7 +37,7 @@ ppLogLine vm (time, event@(WorkerEvent workerId SymbolicWorker _)) =
ppLogLine vm (time, event) =
((timePrefix time <> " ") <>) <$> ppCampaignEventLog vm event
ppCampaignEventLog :: (MonadReader Env m, MonadIO m) => VM Concrete RealWorld -> CampaignEvent -> m String
ppCampaignEventLog :: (MonadReader Env m, MonadIO m) => VM Concrete -> CampaignEvent -> m String
ppCampaignEventLog vm ev = (ppCampaignEvent ev <>) <$> ppTxIfHas where
ppTxIfHas = case ev of
(WorkerEvent _ _ (TestFalsified test)) -> ("\n Call sequence:\n" <>) . unlines <$> mapM (ppTx vm $ length (nub $ (.src) <$> test.reproducer) /= 1) test.reproducer
@@ -71,7 +70,7 @@ ppCampaign workerStates = do
-- | Given rules for pretty-printing associated addresses, and whether to print
-- them, pretty-print a 'Transaction'.
ppTx :: (MonadReader Env m, MonadIO m) => VM Concrete RealWorld -> Bool -> Tx -> m String
ppTx :: (MonadReader Env m, MonadIO m) => VM Concrete -> Bool -> Tx -> m String
ppTx _ _ Tx { call = NoCall, delay } =
pure $ "*wait*" <> ppDelay delay
ppTx vm printName tx = do
@@ -95,7 +94,7 @@ ppTx vm printName tx = do
Nothing -> ""
Just l -> " «" <> T.unpack l <> "»"
contractNameForAddr :: (MonadReader Env m, MonadIO m) => VM Concrete RealWorld -> Addr -> m Text
contractNameForAddr :: (MonadReader Env m, MonadIO m) => VM Concrete -> Addr -> m Text
contractNameForAddr vm addr = do
case Map.lookup (LitAddr addr) (vm ^. #env % #contracts) of
Just contract -> do
@@ -140,7 +139,7 @@ ppCorpus = do
pure $ "Corpus size: " <> show (corpusSize corpus)
-- | Pretty-print the status of a solved test.
ppFail :: (MonadReader Env m, MonadIO m) => Maybe (Int, Int) -> VM Concrete RealWorld -> [Tx] -> m String
ppFail :: (MonadReader Env m, MonadIO m) => Maybe (Int, Int) -> VM Concrete -> [Tx] -> m String
ppFail _ _ [] = pure "failed with no transactions made ⁉️ "
ppFail b vm xs = do
let status = case b of
@@ -153,7 +152,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, MonadIO m) => Maybe (Int, Int) -> VM Concrete RealWorld -> [(Tx, VM Concrete RealWorld)] -> m String
ppFailWithTraces :: (MonadReader Env m, MonadIO m) => Maybe (Int, Int) -> VM Concrete -> [(Tx, VM Concrete)] -> m String
ppFailWithTraces _ _ [] = pure "failed with no transactions made ⁉️ "
ppFailWithTraces b finalVM results = do
dappInfo <- asks (.dapp)
@@ -171,7 +170,7 @@ ppFailWithTraces b finalVM results = do
-- | Pretty-print the status of a test.
ppTS :: (MonadReader Env m, MonadIO m) => TestState -> VM Concrete RealWorld -> [Tx] -> m String
ppTS :: (MonadReader Env m, MonadIO m) => TestState -> VM Concrete -> [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! 🎉"
@@ -182,7 +181,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, MonadIO m) => TestState -> VM Concrete RealWorld -> [Tx] -> m String
ppOPT :: (MonadReader Env m, MonadIO m) => TestState -> VM Concrete -> [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! 🎉"
@@ -193,7 +192,7 @@ ppOPT (Large n) vm l = do
ppOptimized (if n < m then Just (n, m) else Nothing) vm l
-- | Pretty-print the status of an optimized test.
ppOptimized :: (MonadReader Env m, MonadIO m) => Maybe (Int, Int) -> VM Concrete RealWorld -> [Tx] -> m String
ppOptimized :: (MonadReader Env m, MonadIO m) => Maybe (Int, Int) -> VM Concrete -> [Tx] -> m String
ppOptimized _ _ [] = pure "Call sequence:\n(no transactions)"
ppOptimized b vm xs = do
let status = case b of
+7 -8
View File
@@ -7,9 +7,8 @@ import Brick.AttrMap qualified as A
import Brick.Widgets.Border
import Brick.Widgets.Center
import Brick.Widgets.Dialog qualified as B
import Control.Monad.Reader (MonadReader, asks, ask)
import Control.Monad.IO.Class (MonadIO)
import Control.Monad.ST (RealWorld)
import Control.Monad.Reader (MonadReader, asks, ask)
import Data.List (nub, intersperse, sortBy)
import Data.Map (Map)
import Data.Map qualified as Map
@@ -26,19 +25,19 @@ import Paths_echidna qualified (version)
import Text.Printf (printf)
import Text.Wrap
import EVM.Format (showTraceTree)
import EVM.Types (Addr, Contract, W256, VM(..), VMType(Concrete))
import Echidna.ABI
import Echidna.Types.Campaign
import Echidna.Types.Config
import Echidna.Types.Test
import Echidna.Types.Tx (Tx(..))
import Echidna.Types.Worker
import Echidna.Types.Worker
import Echidna.UI.Report
import Echidna.Utility (timePrefix)
import Echidna.Worker (ppCampaignEvent)
import EVM.Format (showTraceTree)
import EVM.Types (Addr, Contract, W256, VM(..), VMType(Concrete))
data UIState = UIState
{ status :: UIStateStatus
, campaigns :: [WorkerState]
@@ -344,7 +343,7 @@ tsWidget (Large n) test = do
titleWidget :: Widget n
titleWidget = str "Call sequence" <+> str ":"
tracesWidget :: MonadReader Env m => VM Concrete RealWorld -> m (Widget n)
tracesWidget :: MonadReader Env m => VM Concrete -> m (Widget n)
tracesWidget vm = do
dappInfo <- asks (.dapp)
-- TODO: showTraceTree does coloring with ANSI escape codes, we need to strip
@@ -416,7 +415,7 @@ shrinkWidget b test =
where
showWorker = maybe "" (\i -> " (worker " <> show i <> ")") test.workerId
seqWidget :: (MonadReader Env m, MonadIO m) => VM Concrete RealWorld -> [Tx] -> m (Widget Name)
seqWidget :: (MonadReader Env m, MonadIO m) => VM Concrete -> [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 ..]
+1 -1
View File
@@ -7,9 +7,9 @@ import Data.Aeson
import Data.Text (unpack)
import Echidna.ABI (encodeSig)
import Echidna.Types.Test
import Echidna.Types.Campaign
import Echidna.Types.Config (Env(..), EConfig(..))
import Echidna.Types.Test
import Echidna.Types.Worker
import Echidna.Utility (getTimestamp)
+1 -1
View File
@@ -3,8 +3,8 @@
module Main where
import Control.Monad (unless, forM_)
import Control.Monad.Reader (runReaderT, liftIO)
import Control.Monad.Random (getRandomR)
import Control.Monad.Reader (runReaderT, liftIO)
import Data.Aeson.Key qualified as Aeson.Key
import Data.Char (toLower)
import Data.Function ((&))
+6 -8
View File
@@ -21,13 +21,9 @@ module Common
, checkCoverageUsesCorpusDir
) where
import Test.Tasty (TestTree)
import Test.Tasty.HUnit (testCase, assertBool)
import Control.Monad (forM_)
import Control.Monad.Reader (runReaderT)
import Control.Monad.Random (getRandomR)
import Control.Monad.ST (RealWorld)
import Control.Monad.Reader (runReaderT)
import Data.DoubleWord (Int256)
import Data.Function ((&))
import Data.IORef
@@ -38,17 +34,19 @@ import Data.Maybe (isJust)
import Data.SemVer (Version, version, fromText)
import Data.Text (Text, pack)
import System.Process (readProcess)
import Test.Tasty (TestTree)
import Test.Tasty.HUnit (testCase, assertBool)
import EVM.Solidity (Contracts(..), BuildOutput(..), SolcContract(..))
import EVM.Types hiding (Env, Gas)
import Echidna (mkEnv, prepareContract)
import Echidna.Config (parseConfig, defaultConfig)
import Echidna.Campaign (runWorker)
import Echidna.Config (parseConfig, defaultConfig)
import Echidna.Solidity (selectMainContract, mkTests, loadSpecified, compileContracts)
import Echidna.Test (checkETest)
import Echidna.Types.Config (Env(..), EConfig(..), EConfigWithUsage(..))
import Echidna.Types.Campaign
import Echidna.Types.Config (Env(..), EConfig(..), EConfigWithUsage(..))
import Echidna.Types.Signature (ContractName)
import Echidna.Types.Solidity (SolConf(..))
import Echidna.Types.Test
@@ -148,7 +146,7 @@ loadSolTests
:: EConfig
-> BuildOutput
-> Maybe Text
-> IO (VM Concrete RealWorld, Env, [EchidnaTest])
-> IO (VM Concrete, Env, [EchidnaTest])
loadSolTests cfg buildOutput name = do
let solConf = cfg.solConf
(Contracts contractMap) = buildOutput.contracts
+5 -5
View File
@@ -1,20 +1,20 @@
import Test.Tasty (defaultMain, testGroup)
import System.Directory (withCurrentDirectory)
import Test.Tasty (defaultMain, testGroup)
import Tests.ABIv2 (abiv2Tests)
import Tests.Assertion (assertionTests)
import Tests.Cheat (cheatTests)
import Tests.Compile (compilationTests)
import Tests.Coverage (coverageTests)
import Tests.Config (configTests)
import Tests.Coverage (coverageTests)
import Tests.Dapptest (dapptestTests)
import Tests.Encoding (encodingJSONTests)
import Tests.Integration (integrationTests)
import Tests.Optimization (optimizationTests)
import Tests.Overflow (overflowTests)
import Tests.Research (researchTests)
import Tests.Values (valuesTests)
import Tests.Seed (seedTests)
import Tests.Dapptest (dapptestTests)
import Tests.Cheat (cheatTests)
import Tests.Symbolic (symbolicTests)
import Tests.Values (valuesTests)
main :: IO ()
main = withCurrentDirectory "./tests/solidity" . defaultMain $
-1
View File
@@ -3,7 +3,6 @@ module Tests.Assertion (assertionTests) where
import Test.Tasty (TestTree, testGroup)
import Common (testContract, testContract', testContractV, solcV, solved, solvedUsing, passed)
import Echidna.Types.Worker (WorkerType(..))
assertionTests :: TestTree
-1
View File
@@ -3,7 +3,6 @@ module Tests.Cheat (cheatTests) where
import Test.Tasty (TestTree, testGroup)
import Common (testContract', solcV, solved, passed)
import Echidna.Types.Worker (WorkerType(..))
cheatTests :: TestTree
+4 -5
View File
@@ -1,16 +1,15 @@
module Tests.Compile (compilationTests) where
import Control.Monad (void)
import Control.Monad.Catch (catch)
import Data.Text (Text)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, assertBool)
import Common (testConfig, loadSolTests)
import Control.Monad (void)
import Control.Monad.Catch (catch)
import Data.Text (Text)
import Echidna.Solidity (compileContracts)
import Echidna.Types.Solidity (SolException(..))
import Echidna.Types.Config (EConfig(..))
import Echidna.Types.Solidity (SolException(..))
compilationTests :: TestTree
compilationTests = testGroup "Compilation and loading tests"
+8 -9
View File
@@ -1,18 +1,17 @@
module Tests.Config (configTests) where
import Control.Monad (void)
import Data.Function ((&))
import Data.Maybe (isJust, isNothing)
import Data.Yaml qualified as Y
import Optics.Core (sans)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, assertBool, assertFailure)
import Optics.Core (sans)
import Control.Monad (void)
import Data.Function ((&))
import Data.Yaml qualified as Y
import Echidna.Types.Config (EConfigWithUsage(..), EConfig(..))
import Echidna.Types.Campaign (CampaignConf(..))
import Echidna.Types.Tx (TxConf(..))
import Echidna.Config (defaultConfig, parseConfig)
import Data.Maybe (isJust, isNothing)
import Echidna.Types.Campaign (CampaignConf(..))
import Echidna.Types.Config (EConfigWithUsage(..), EConfig(..))
import Echidna.Types.Tx (TxConf(..))
configTests :: TestTree
configTests = testGroup "Configuration tests" $
-1
View File
@@ -3,7 +3,6 @@ module Tests.Dapptest (dapptestTests) where
import Test.Tasty (TestTree, testGroup)
import Common (testContract', solcV, solved, passed)
import Echidna.Types.Worker (WorkerType(..))
dapptestTests :: TestTree
+5 -4
View File
@@ -1,13 +1,14 @@
module Tests.Encoding (encodingJSONTests) where
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (Arbitrary(..), Gen, (===), property, testProperty, resize)
import Data.Aeson (encode, decode)
import Data.Text (pack)
import Echidna.Types.Tx (TxCall(..), Tx(..))
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (Arbitrary(..), Gen, (===), property, testProperty, resize)
import EVM.Types (Addr, W256)
import Echidna.Types.Tx (TxCall(..), Tx(..))
instance Arbitrary Addr where
arbitrary = fromInteger <$> arbitrary
+6 -5
View File
@@ -1,14 +1,15 @@
module Tests.Integration (integrationTests) where
import Test.Tasty (TestTree, testGroup)
import Common (testContract, testContractV, solcV, testContract', checkConstructorConditions, passed, solved, solvedLen, solvedWith, solvedWithout)
import Data.Functor ((<&>))
import Data.Text (unpack)
import Echidna.Types.Worker (WorkerType(..))
import Echidna.Types.Tx (TxCall(..))
import Test.Tasty (TestTree, testGroup)
import EVM.ABI (AbiValue(..))
import Common (testContract, testContractV, solcV, testContract', checkConstructorConditions, passed, solved, solvedLen, solvedWith, solvedWithout)
import Echidna.Types.Tx (TxCall(..))
import Echidna.Types.Worker (WorkerType(..))
integrationTests :: TestTree
integrationTests = testGroup "Solidity Integration Testing"
[ testContract "basic/true.sol" Nothing
-1
View File
@@ -3,7 +3,6 @@ module Tests.Research (researchTests) where
import Test.Tasty (TestTree, testGroup)
import Common (testContract, testContract', solcV, solved)
import Echidna.Types.Worker (WorkerType(..))
researchTests :: TestTree
+2 -2
View File
@@ -1,11 +1,11 @@
module Tests.Seed (seedTests) where
import Data.Function ((&))
import Data.IORef (readIORef)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, assertBool)
import Common (runContract, overrideQuiet)
import Data.Function ((&))
import Data.IORef (readIORef)
import Echidna.Config (defaultConfig)
import Echidna.Mutator.Corpus (defaultMutationConsts)
import Echidna.Types.Campaign
+1
View File
@@ -1,6 +1,7 @@
module Tests.Symbolic (symbolicTests) where
import Test.Tasty (TestTree, testGroup)
import Common (testContract', solcV, solved, verified)
import Echidna.Types.Worker (WorkerType(..))
-1
View File
@@ -3,7 +3,6 @@ module Tests.Values (valuesTests) where
import Test.Tasty (TestTree, testGroup)
import Common (testContract, testContractV, testContract', solcV, solved, solvedLen)
import Echidna.Types.Worker (WorkerType(..))
valuesTests :: TestTree
+1 -1
View File
@@ -6,7 +6,7 @@ packages:
extra-deps:
- git: https://github.com/argotorg/hevm.git
commit: 0d9e2744903d160b175cd9e727660b493d9fac6f
commit: 9ba5e52fc7ec7ae6f7f3a25d5ee426625d2aa9d3
- smt2-parser-0.1.0.1@sha256:1e1a4565915ed851c13d1e6b8bb5185cf5d454da3b43170825d53e221f753d77,1421
- spawn-0.3@sha256:b91e01d8f2b076841410ae284b32046f91471943dc799c1af77d666c72101f02,1162