mirror of
https://github.com/crytic/echidna.git
synced 2026-10-12 02:12:32 +00:00
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:
co-authored by
gustavo-grieco
parent
6c3fe007a2
commit
a31c5f8c26
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(..))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
module Echidna.SymExec.Symbolic where
|
||||
|
||||
import Data.ByteString (ByteString)
|
||||
|
||||
import EVM.Types (Expr(..), EType(..), W256, Addr)
|
||||
|
||||
forceBuf :: Expr Buf -> ByteString
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,6 +1,7 @@
|
||||
module Echidna.Types.Corpus where
|
||||
|
||||
import Data.Set (Set, size)
|
||||
|
||||
import Echidna.Types.Tx (Tx)
|
||||
|
||||
type Corpus = Set (Int, [Tx])
|
||||
|
||||
@@ -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,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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 ..]
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 $
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" $
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,7 @@
|
||||
module Tests.Symbolic (symbolicTests) where
|
||||
|
||||
import Test.Tasty (TestTree, testGroup)
|
||||
|
||||
import Common (testContract', solcV, solved, verified)
|
||||
import Echidna.Types.Worker (WorkerType(..))
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user