diff --git a/flake.nix b/flake.nix index 4ac1b6f65..54aa44c6f 100644 --- a/flake.nix +++ b/flake.nix @@ -38,8 +38,16 @@ ''; }; + hevm = pkgs.haskell.lib.dontCheck ( + pkgs.haskellPackages.callCabal2nix "hevm" (pkgs.fetchFromGitHub { + owner = "ethereum"; + repo = "hevm"; + rev = "release/0.50.5"; + sha256 = "sha256-Vi6kL1nJdujfS1oePwqks1owVPlS5Dd5hAn0r8Rpw+k="; + }) { secp256k1 = pkgs.secp256k1; }); + echidna = with pkgs; lib.pipe - (haskellPackages.callCabal2nix "echidna" ./. { }) + (haskellPackages.callCabal2nix "echidna" ./. { inherit hevm; }) [ (haskell.lib.compose.addTestToolDepends [ haskellPackages.hpack slither-analyzer solc ]) (haskell.lib.compose.disableCabalFlag "static") diff --git a/lib/Echidna.hs b/lib/Echidna.hs index 4dcd4bb6b..c44c88e87 100644 --- a/lib/Echidna.hs +++ b/lib/Echidna.hs @@ -8,7 +8,7 @@ import Data.Map.Strict qualified as Map import Data.Set qualified as Set import System.FilePath (()) -import EVM hiding (Env, env, contracts) +import EVM hiding (Env) import EVM.ABI (AbiValue(AbiAddress)) import EVM.Solidity (SolcContract(..)) @@ -62,13 +62,13 @@ prepareContract env contracts solFiles specifiedContract seed = do echidnaTests = createTests solConf.testMode solConf.testDestruction testNames - vm._state._contract + vm.state.contract funs eventMap = Map.unions $ map (.eventMap) contracts world = mkWorld solConf eventMap signatureMap specifiedContract slitherInfo - deployedAddresses = Set.fromList $ AbiAddress <$> Map.keys vm._env._contracts + deployedAddresses = Set.fromList $ AbiAddress <$> Map.keys vm.env.contracts constants = enhanceConstants slitherInfo <> timeConstants <> extremeConstants diff --git a/lib/Echidna/ABI.hs b/lib/Echidna/ABI.hs index ebe54b0cb..6c6d6d10e 100644 --- a/lib/Echidna/ABI.hs +++ b/lib/Echidna/ABI.hs @@ -30,7 +30,7 @@ import Data.Word (Word8) import Numeric (showHex) import EVM.ABI hiding (genAbiValue) -import EVM.Types (Addr, abiKeccak, W256) +import EVM.Types (Addr, abiKeccak, W256, FunctionSelector(..)) import Echidna.Mutator.Array (mutateLL, replaceAt) import Echidna.Types.Random @@ -95,7 +95,7 @@ encodeSigWithName cn (n, ts) = last (T.split (==':') cn) <> "." <> n <> "(" <> T.intercalate "," (abiTypeSolidity <$> ts) <> ")" -- | Get the signature of a solidity method -hashSig :: Text -> FunctionHash +hashSig :: Text -> FunctionSelector hashSig = abiKeccak . TE.encodeUtf8 -- | Configuration necessary for generating new 'SolCall's. Don't construct this @@ -377,5 +377,5 @@ genInteractionsM genDict solSignatures = abiCalldata :: Text -> Vector AbiValue -> ByteString abiCalldata s xs = BSLazy.toStrict . runPut $ do - putWord32be (abiKeccak (encodeUtf8 s)) + putWord32be (abiKeccak (encodeUtf8 s)).unFunctionSelector putAbi (AbiTuple xs) diff --git a/lib/Echidna/Campaign.hs b/lib/Echidna/Campaign.hs index eef4ef969..723591518 100644 --- a/lib/Echidna/Campaign.hs +++ b/lib/Echidna/Campaign.hs @@ -2,8 +2,9 @@ module Echidna.Campaign where +import Optics.Core + import Control.DeepSeq (force) -import Control.Lens import Control.Monad (foldM, replicateM, when, unless, void) import Control.Monad.Catch (MonadCatch(..), MonadThrow(..)) import Control.Monad.Random.Strict (MonadRandom, RandT, evalRandT) @@ -23,8 +24,8 @@ import Data.Set qualified as Set import Data.Text (Text) import System.Random (mkStdGen) -import EVM (Contract, VM(..), VMResult(..), bytecode, cheatCode) -import EVM qualified (Env(..)) +import EVM hiding (Env, Frame(state), VM(state)) +import EVM (VM) import EVM.ABI (getAbi, AbiType(AbiAddressType), AbiValue(AbiAddress)) import EVM.Types (Addr, Expr(ConcreteBuf)) @@ -104,7 +105,7 @@ runCampaign callback vm world tests dict initialCorpus = do metaCacheRef <- asks (.metadataCache) fetchContractCacheRef <- asks (.fetchContractCache) external <- liftIO $ Map.mapMaybe id <$> readIORef fetchContractCacheRef - liftIO $ writeIORef metaCacheRef (mkMemo (vm._env._contracts <> external)) + liftIO $ writeIORef metaCacheRef (mkMemo (vm.env.contracts <> external)) let covMap = fromMaybe mempty conf.knownCoverage @@ -145,7 +146,7 @@ runCampaign callback vm world tests dict initialCorpus = do | otherwise -> void $ lift callback - fuzz = randseq vm._env._contracts world >>= callseq vm + fuzz = randseq vm.env.contracts world >>= callseq vm continue = do runUpdate (shrinkTest vm) @@ -215,7 +216,7 @@ callseq vm txSeq = do let -- compute the addresses not present in the old VM via set difference - newAddrs = Map.keys $ vm'._env._contracts \\ vm._env._contracts + newAddrs = Map.keys $ vm'.env.contracts \\ vm.env.contracts -- and construct a set to union to the constants table diffs = Map.fromList [(AbiAddressType, Set.fromList $ AbiAddress <$> newAddrs)] -- Now we try to parse the return values as solidity constants, and add then to the 'GenDict' diff --git a/lib/Echidna/Config.hs b/lib/Echidna/Config.hs index 32a9f0a8e..4ea125869 100644 --- a/lib/Echidna/Config.hs +++ b/lib/Echidna/Config.hs @@ -80,7 +80,7 @@ instance FromJSON EConfigWithUsage where psender <- v ..:? "psender" ..!= 0x10000 fprefix <- v ..:? "prefix" ..!= "echidna_" let goal fname = if (fprefix <> "revert_") `isPrefixOf` fname then ResRevert else ResTrue - classify fname vm = maybe ResOther classifyRes vm._result == goal fname + classify fname vm = maybe ResOther classifyRes vm.result == goal fname pure $ TestConf classify (const psender) campaignConfParser = CampaignConf diff --git a/lib/Echidna/Deploy.hs b/lib/Echidna/Deploy.hs index a9df333bf..51b0bf63d 100644 --- a/lib/Echidna/Deploy.hs +++ b/lib/Echidna/Deploy.hs @@ -52,7 +52,7 @@ deployBytecodes' cs src initialVM = foldM deployOne initialVM cs deployOne vm (dst, bytecode) = do vm' <- flip execStateT vm $ execTx $ createTx (bytecode <> zeros) src dst unlimitedGasPerBlock (0, 0) - case vm'._result of + case vm'.result of Just (VMSuccess _) -> pure vm' _ -> do di <- asks (.dapp) diff --git a/lib/Echidna/Etheno.hs b/lib/Echidna/Etheno.hs index 924339daa..d87e0190f 100644 --- a/lib/Echidna/Etheno.hs +++ b/lib/Echidna/Etheno.hs @@ -4,12 +4,14 @@ module Echidna.Etheno where import Prelude hiding (Word) +import Optics.Core +import Optics.State.Operators + import Control.Exception (Exception) -import Control.Lens import Control.Monad (void) import Control.Monad.Catch (MonadThrow, throwM) import Control.Monad.Fail qualified as M (MonadFail(..)) -import Control.Monad.State.Strict (MonadState, get, put, execStateT) +import Control.Monad.State.Strict (MonadState, get, put, execStateT, gets) import Data.Aeson (FromJSON(..), (.:), withObject, eitherDecodeFileStrict) import Data.ByteString.Base16 qualified as BS16 (decode) import Data.ByteString.Char8 (ByteString) @@ -130,14 +132,14 @@ loadEthenoBatch ffi fp = do initAddress :: MonadState VM m => Addr -> m () initAddress addr = do - cs <- use (env . EVM.contracts) + cs <- gets (.env.contracts) if addr `member` cs then pure () - else env . EVM.contracts . at addr .= Just account + else #env % #contracts % at addr .= Just account where account = initialContract (RuntimeCode (ConcreteRuntimeCode mempty)) - & set nonce 0 - & set balance 100000000000000000000 -- default balance for EOAs in etheno + & set #nonce 0 + & set #balance 100000000000000000000 -- default balance for EOAs in etheno crashWithQueryError :: (MonadState VM m, MonadFail m, MonadThrow m) @@ -174,7 +176,7 @@ execEthenoTxs et = do (VMFailure x, _) -> vmExcept x >> M.fail "impossible" (VMSuccess (ConcreteBuf bc), ContractCreated _ ca _ _ _ _) -> do - env . contracts . at ca . _Just . contractcode .= InitCode mempty mempty + #env % #contracts % at ca % _Just % #contractcode .= InitCode mempty mempty fromEVM $ do replaceCodeOfSelf (RuntimeCode (ConcreteRuntimeCode bc)) loadContract ca diff --git a/lib/Echidna/Events.hs b/lib/Echidna/Events.hs index 00550fc6a..c0d1e24a7 100644 --- a/lib/Echidna/Events.hs +++ b/lib/Echidna/Events.hs @@ -36,10 +36,10 @@ extractEvents decodeErrors dappInfo vm = ++ catMaybes (concatMap flatten (fmap (fmap showTrace) forest)) where showTrace trace = - let ?context = DappContext { info = dappInfo, env = vm._env._contracts } in - let codehash' = fromJust $ maybeLitWord trace._traceContract._codehash + let ?context = DappContext { info = dappInfo, env = vm.env.contracts } in + let codehash' = fromJust $ maybeLitWord trace.contract.codehash maybeContractName = maybeContractNameFromCodeHash dappInfo codehash' - in case trace._traceData of + in case trace.tracedata of EventTrace addr bytes (topic:_) -> case Map.lookup (forceLit topic) dappInfo.eventMap of Just (Event name _ types) -> @@ -78,7 +78,7 @@ maybeContractNameFromCodeHash info codeHash = contractToName <$> maybeContract decodeRevert :: Bool -> VM -> Maybe Text decodeRevert decodeErrors vm = - case vm._result of + case vm.result of Just (VMFailure (Revert (ConcreteBuf bs))) -> decodeRevertMsg decodeErrors bs _ -> Nothing diff --git a/lib/Echidna/Exec.hs b/lib/Echidna/Exec.hs index 1219e7951..8f361c22a 100644 --- a/lib/Echidna/Exec.hs +++ b/lib/Echidna/Exec.hs @@ -4,8 +4,11 @@ module Echidna.Exec where -import Control.Lens -import Control.Monad (forM_, when) +import Optics.Core +import Optics.State +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)) import Control.Monad.Reader (MonadReader, asks) @@ -19,7 +22,7 @@ import Data.Vector qualified as V import Data.Vector.Unboxed.Mutable qualified as V import System.Process (readProcessWithExitCode) -import EVM hiding (pc, Env, cache, contract, tx, value) +import EVM hiding (Env) import EVM.ABI import EVM.Exec (exec, vmForEthrunCreation) import EVM.Fetch qualified @@ -84,12 +87,12 @@ execTxWith l onErr executeTx tx = do if hasSelfdestructed vm tx.dst then pure (VMFailure (Revert (ConcreteBuf "")), 0) else do - l . traces .= emptyEvents + l % #traces .= emptyEvents vmBeforeTx <- use l l %= execState (setupTx tx) - gasLeftBeforeTx <- use $ l . state . gas + gasLeftBeforeTx <- use $ l % #state % #gas vmResult <- runFully - gasLeftAfterTx <- use $ l . state . gas + gasLeftAfterTx <- use $ l % #state % #gas handleErrorsAndConstruction vmResult vmBeforeTx pure (vmResult, gasLeftBeforeTx - gasLeftAfterTx) where @@ -118,7 +121,7 @@ execTxWith l onErr executeTx tx = do ret <- liftIO $ safeFetchContractFrom rpcBlock rpcUrl addr case ret of -- TODO: fix hevm to not return an empty contract in case of an error - Just contract | contract._contractcode /= EVM.RuntimeCode (EVM.ConcreteRuntimeCode "") -> do + Just contract | contract.contractcode /= EVM.RuntimeCode (EVM.ConcreteRuntimeCode "") -> do metaCacheRef <- asks (.metadataCache) metaCache <- liftIO $ readIORef metaCacheRef let bc = forceBuf (contract ^. bytecode) @@ -187,25 +190,25 @@ execTxWith l onErr executeTx tx = do -- (`vmResult`) of executing transaction `tx`. handleErrorsAndConstruction vmResult vmBeforeTx = case (vmResult, tx.call) of (Reversion, _) -> do - tracesBeforeVMReset <- use $ l . traces - codeContractBeforeVMReset <- use $ l . state . codeContract - calldataBeforeVMReset <- use $ l . state . calldata - callvalueBeforeVMReset <- use $ l . state . callvalue + tracesBeforeVMReset <- use $ l % #traces + codeContractBeforeVMReset <- use $ l % #state % #codeContract + calldataBeforeVMReset <- use $ l % #state % #calldata + callvalueBeforeVMReset <- use $ l % #state % #callvalue -- If a transaction reverts reset VM to state before the transaction. l .= vmBeforeTx -- Undo reset of some of the VM state. -- Otherwise we'd loose all information about the reverted transaction like -- contract address, calldata, result and traces. - l . result ?= vmResult - l . state . calldata .= calldataBeforeVMReset - l . state . callvalue .= callvalueBeforeVMReset - l . traces .= tracesBeforeVMReset - l . state . codeContract .= codeContractBeforeVMReset + l % #result ?= vmResult + l % #state % #calldata .= calldataBeforeVMReset + l % #state % #callvalue .= callvalueBeforeVMReset + l % #traces .= tracesBeforeVMReset + l % #state % #codeContract .= codeContractBeforeVMReset (VMFailure x, _) -> onErr x (VMSuccess (ConcreteBuf bytecode'), SolCreate _) -> -- Handle contract creation. l %= execState (do - env . contracts . at tx.dst . _Just . contractcode .= InitCode mempty mempty + #env % #contracts % at tx.dst % _Just % #contractcode .= InitCode mempty mempty replaceCodeOfSelf (RuntimeCode (ConcreteRuntimeCode bytecode')) loadContract tx.dst) _ -> pure () @@ -224,7 +227,7 @@ execTx :: (MonadIO m, MonadState VM m, MonadReader Env m, MonadThrow m) => Tx -> m (VMResult, Gas) -execTx = execTxWith id vmExcept $ fromEVM exec +execTx = execTxWith equality' vmExcept $ fromEVM exec -- | A type alias for the context we carry while executing instructions type CoverageContext = (CoverageMap, Bool, Maybe (BS.ByteString, Int)) @@ -267,7 +270,7 @@ execTxWithCov tx cov = do -- | Repeatedly exec a step and add coverage until we have an end result loop :: MetadataCache -> VM -> CoverageContext -> IO (VMResult, VM, CoverageContext) - loop cache !vm !cc = case vm._result of + loop cache !vm !cc = case vm.result of Nothing -> addCoverage cache vm cc >>= loop cache (stepVM vm) Just r -> pure (r, vm, cc) @@ -283,7 +286,7 @@ execTxWithCov tx cov = do case Map.lookup meta cm of Nothing -> do let size = BS.length . forceBuf . view bytecode . fromJust $ - Map.lookup vm._state._contract vm._env._contracts + Map.lookup vm.state.contract vm.env.contracts if size > 0 then do vec <- V.new size -- We use -1 for opIx to indicate that the location was not covered @@ -304,17 +307,17 @@ execTxWithCov tx cov = do pure (cm, new, Just (meta, pc)) -- | Get the VM's current execution location - currentCovLoc vm = (vm._state._pc, fromMaybe 0 $ vmOpIx vm, length vm._frames) + currentCovLoc vm = (vm.state.pc, fromMaybe 0 $ vmOpIx vm, length vm.frames) -- | Get the current contract's bytecode metadata currentMeta cache vm = fromMaybe (error "no contract information on coverage") $ do - buffer <- vm ^? env . contracts . at vm._state._contract . _Just . bytecode + buffer <- vm ^? #env % #contracts % at vm.state.contract % _Just % bytecode let bc = forceBuf buffer pure $ lookupBytecodeMetadata cache bc initialVM :: Bool -> VM initialVM ffi = vmForEthrunCreation mempty - & block . timestamp .~ Lit initialTimestamp - & block . number .~ initialBlockNumber - & env . contracts .~ mempty -- fixes weird nonce issues - & allowFFI .~ ffi + & #block % #timestamp .~ Lit initialTimestamp + & #block % #number .~ initialBlockNumber + & #env % #contracts .~ mempty -- fixes weird nonce issues + & #allowFFI .~ ffi diff --git a/lib/Echidna/Processor.hs b/lib/Echidna/Processor.hs index 6d1be24d6..e00f04cc9 100644 --- a/lib/Echidna/Processor.hs +++ b/lib/Echidna/Processor.hs @@ -25,10 +25,10 @@ import System.Process (StdStream(..), readCreateProcessWithExitCode, proc, std_e import Text.Read (readMaybe) import EVM.ABI (AbiValue(..)) -import EVM.Types (Addr(..)) +import EVM.Types (Addr(..), FunctionSelector) import Echidna.ABI (hashSig, makeNumAbiValues, makeArrayAbiValues) -import Echidna.Types.Signature (ContractName, FunctionName, FunctionHash) +import Echidna.Types.Signature (ContractName, FunctionName) import Echidna.Types.Solidity (SolConf(..)) import Echidna.Utility (measureIO) @@ -46,7 +46,7 @@ instance Exception ProcException -- | This function is used to filter the lists of function names according to the supplied -- contract name (if any) and returns a list of hashes -filterResults :: Maybe ContractName -> Map ContractName [FunctionName] -> [FunctionHash] +filterResults :: Maybe ContractName -> Map ContractName [FunctionName] -> [FunctionSelector] filterResults (Just c) rs = case Map.lookup c rs of Nothing -> filterResults Nothing rs diff --git a/lib/Echidna/RPC.hs b/lib/Echidna/RPC.hs index 429ebc24f..096ded4c8 100644 --- a/lib/Echidna/RPC.hs +++ b/lib/Echidna/RPC.hs @@ -11,7 +11,6 @@ import Data.Text qualified as Text import Data.Text (Text) import Data.Word (Word64) import GHC.Generics (Generic) -import Network.Wreq.Session qualified as Session import System.Environment import Text.Read (readMaybe) @@ -47,12 +46,7 @@ safeFetchSlotFrom rpcBlock rpcUrl addr slot = (\(_e :: SomeException) -> pure $ Just 0) fetchChainId :: Maybe Text -> IO (Maybe W256) -fetchChainId (Just url) = do - sess <- Session.newAPISession - EVM.Fetch.fetchQuery - EVM.Fetch.Latest -- this shouldn't matter - (EVM.Fetch.fetchWithSession url sess) - EVM.Fetch.QueryChainId +fetchChainId (Just url) = EVM.Fetch.fetchChainIdFrom url fetchChainId Nothing = pure Nothing data FetchedContractData = FetchedContractData @@ -68,18 +62,18 @@ instance ToJSONKey W256 where fromFetchedContractData :: FetchedContractData -> Contract fromFetchedContractData contractData = (initialContract (RuntimeCode (ConcreteRuntimeCode contractData.runtimeCode))) - { _nonce = contractData.nonce - , _balance = contractData.balance - , _external = True + { nonce = contractData.nonce + , balance = contractData.balance + , external = True } toFetchedContractData :: Contract -> FetchedContractData toFetchedContractData contract = - let code = case contract._contractcode of + let code = case contract.contractcode of RuntimeCode (ConcreteRuntimeCode c) -> c _ -> error "unexpected code" in FetchedContractData { runtimeCode = code - , nonce = contract._nonce - , balance = contract._balance + , nonce = contract.nonce + , balance = contract.balance } diff --git a/lib/Echidna/Solidity.hs b/lib/Echidna/Solidity.hs index cac244f90..ab0e93462 100644 --- a/lib/Echidna/Solidity.hs +++ b/lib/Echidna/Solidity.hs @@ -1,6 +1,7 @@ module Echidna.Solidity where -import Control.Lens hiding (filtered) +import Optics.Core hiding (filtered) + import Control.Arrow (first) import Control.Monad (when, unless, forM_) import Control.Monad.Catch (MonadThrow(..)) @@ -27,11 +28,10 @@ import System.FilePath (joinPath, splitDirectories, ()) import System.IO (openFile, IOMode(..)) import System.Info (os) -import EVM hiding (Env, env, contract, contracts, path) -import EVM qualified (contracts, env) +import EVM hiding (Env) import EVM.ABI import EVM.Solidity -import EVM.Types (Addr) +import EVM.Types (Addr, FunctionSelector) import Echidna.ABI ( encodeSig, encodeSigWithName, hashSig, fallback @@ -44,7 +44,7 @@ import Echidna.Processor import Echidna.Test (createTests, isAssertionMode, isPropertyMode, isDapptestMode) import Echidna.Types.Config (EConfig(..), Env(..)) import Echidna.Types.Signature - (ContractName, FunctionHash, SolSignature, SignatureMap, getBytecodeMetadata) + (ContractName, SolSignature, SignatureMap, getBytecodeMetadata) import Echidna.Types.Solidity import Echidna.Types.Test (EchidnaTest(..)) import Echidna.Types.Tx @@ -140,13 +140,13 @@ populateAddresses addrs b vm = Set.foldl' (\vm' addr -> if deployed addr then vm' - else vm' & set (EVM.env . EVM.contracts . at addr) (Just account) + else vm' & set (#env % #contracts % at addr) (Just account) ) vm addrs where account = (initialContract (RuntimeCode (ConcreteRuntimeCode mempty))) - { _nonce = 0, _balance = fromInteger b } - deployed addr = addr `member` vm._env._contracts + { nonce = 0, balance = fromInteger b } + deployed addr = addr `member` vm.env.contracts -- | Address to load the first library addrLibrary :: Addr @@ -156,7 +156,7 @@ addrLibrary = 0xff linkLibraries :: [String] -> String linkLibraries [] = "" linkLibraries ls = "--libraries " ++ - iconcatMap (\i x -> concat [x, ":", show $ addrLibrary + toEnum i, ","]) ls + concatMap (\(i,x) -> concat [x, ":", show $ addrLibrary + i, ","]) (zip [0..] ls) -- | Filter methods using a whitelist/blacklist filterMethods :: Text -> Filter -> NonEmpty SolSignature -> [SolSignature] @@ -227,8 +227,8 @@ loadSpecified env name cs = do -- Set up initial VM, either with chosen contract or Etheno initialization file -- need to use snd to add to ABI dict vm = initialVM solConf.allowFFI - & block . gaslimit .~ unlimitedGasPerBlock - & block . maxCodeSize .~ fromIntegral solConf.codeSize + & #block % #gaslimit .~ unlimitedGasPerBlock + & #block % #maxCodeSize .~ fromIntegral solConf.codeSize blank' <- maybe (pure vm) (loadEthenoBatch solConf.allowFFI) solConf.initialize let blank = populateAddresses (Set.insert solConf.deployer solConf.sender) @@ -288,7 +288,7 @@ loadSpecified env name cs = do then execStateT transaction vm3 else return vm3 - case vm4._result of + case vm4.result of Just (VMFailure _) -> throwM SetUpCallFailed _ -> pure (vm4, neFuns, fst <$> tests, abiMapping) @@ -336,8 +336,8 @@ filterFallbacks _ [] [] sm = Map.map f sm filterFallbacks _ _ _ sm = sm prepareHashMaps - :: [FunctionHash] - -> [FunctionHash] + :: [FunctionSelector] + -> [FunctionSelector] -> SignatureMap -> (SignatureMap, Maybe SignatureMap) prepareHashMaps [] _ m = (m, Nothing) -- No constant functions detected @@ -372,7 +372,7 @@ loadSolTests env fp name = do let eventMap = Map.unions $ map (.eventMap) contracts world = World solConf.sender mempty Nothing [] eventMap - echidnaTests = createTests solConf.testMode True testNames vm._state._contract funs + echidnaTests = createTests solConf.testMode True testNames vm.state.contract funs pure (vm, world, echidnaTests) mkLargeAbiInt :: Int -> AbiValue diff --git a/lib/Echidna/Test.hs b/lib/Echidna/Test.hs index 9891a6983..ff8915926 100644 --- a/lib/Echidna/Test.hs +++ b/lib/Echidna/Test.hs @@ -24,6 +24,7 @@ import Echidna.Types.Buffer (forceBuf) import Echidna.Types.Config import Echidna.Types.Signature (SolSignature) import Echidna.Types.Test +import Echidna.Types.Test qualified as Test import Echidna.Types.Tx (Tx, TxConf(..), basicTx, TxResult(..), getResult) --- | Possible responses to a call to an Echidna test: @true@, @false@, @REVERT@, and ???. @@ -41,7 +42,7 @@ classifyRes _ = ResOther getResultFromVM :: VM -> TxResult getResultFromVM vm = - case vm._result of + case vm.result of Just r -> getResult r Nothing -> error "getResultFromVM failed" @@ -117,9 +118,9 @@ updateOpenTest -> (TestValue, Events, TxResult) -> EchidnaTest updateOpenTest test txs _ (BoolValue False,es,r) = - test { state = Large (-1), reproducer = txs, events = es, result = r } + test { Test.state = Large (-1), reproducer = txs, events = es, result = r } updateOpenTest test _ i (BoolValue True,_,_) = - test { state = Open (i + 1) } + test { Test.state = Open (i + 1) } updateOpenTest test txs i (IntValue v',es,r) = if v' > v then test { state = Open (i + 1) @@ -128,7 +129,7 @@ updateOpenTest test txs i (IntValue v',es,r) = , events = es , result = r } else - test { state = Open (i + 1) } + test { Test.state = Open (i + 1) } where v = case test.value of IntValue x -> x @@ -156,7 +157,7 @@ checkProperty -> m (TestValue, VM) checkProperty f a = do vm <- get - case vm._result of + case vm.result of Just (VMSuccess _) -> do TestConf{classifier, testSender} <- asks (.cfg.testConf) (_, vm') <- runTx f testSender a @@ -195,7 +196,7 @@ checkOptimization f a = do TestConf _ s <- asks (.cfg.testConf) (vm, vm') <- runTx f s a put vm -- restore EVM state - pure (getIntFromResult (vm'._result), vm') + pure (getIntFromResult vm'.result, vm') checkStatefulAssertion :: (MonadReader Env m, MonadState VM m, MonadThrow m) @@ -209,12 +210,12 @@ checkStatefulAssertion sig addr = do -- Whether the last transaction called the function `sig`. isCorrectFn = BS.isPrefixOf (BS.take 4 (abiCalldata (encodeSig sig) mempty)) - (forceBuf vm._state._calldata) + (forceBuf vm.state.calldata) -- Whether the last transaction executed a function on the contract `addr`. - isCorrectAddr = addr == vm._state._codeContract + isCorrectAddr = addr == vm.state.codeContract isCorrectTarget = isCorrectFn && isCorrectAddr -- Whether the last transaction executed opcode 0xfe, meaning an assertion failure. - isAssertionFailure = case vm._result of + isAssertionFailure = case vm.result of Just (VMFailure (UnrecognizedOpcode 0xfe)) -> True _ -> False -- Test always passes if it doesn't target the last executed contract and function. @@ -236,17 +237,17 @@ checkDapptestAssertion sig addr = do vm <- get let -- Whether the last transaction has any value - hasValue = vm._state._callvalue /= Lit 0 + hasValue = vm.state.callvalue /= Lit 0 -- Whether the last transaction called the function `sig`. isCorrectFn = BS.isPrefixOf (BS.take 4 (abiCalldata (encodeSig sig) mempty)) - (forceBuf vm._state._calldata) - isAssertionFailure = case vm._result of + (forceBuf vm.state.calldata) + isAssertionFailure = case vm.result of Just (VMFailure (Revert (ConcreteBuf bs))) -> not $ BS.isSuffixOf assumeMagicReturnCode bs Just (VMFailure _) -> True _ -> False - isCorrectAddr = addr == vm._state._codeContract + isCorrectAddr = addr == vm.state.codeContract isCorrectTarget = isCorrectFn && isCorrectAddr isFailure = not hasValue && (isCorrectTarget && isAssertionFailure) pure (BoolValue (not isFailure), vm) @@ -270,13 +271,12 @@ checkAssertionEvent = any (T.isPrefixOf "AssertionFailed(") checkSelfDestructedTarget :: Addr -> DappInfo -> VM -> TestValue checkSelfDestructedTarget addr _ vm = - let selfdestructs' = vm._tx._substate._selfdestructs + let selfdestructs' = vm.tx.substate.selfdestructs in BoolValue $ addr `notElem` selfdestructs' checkAnySelfDestructed :: DappInfo -> VM -> TestValue checkAnySelfDestructed _ vm = - let sd = vm._tx._substate._selfdestructs - in BoolValue $ null sd + BoolValue $ null vm.tx.substate.selfdestructs checkPanicEvent :: T.Text -> Events -> Bool checkPanicEvent n = any (T.isPrefixOf ("Panic(" <> n <> ")")) diff --git a/lib/Echidna/Transaction.hs b/lib/Echidna/Transaction.hs index abac1f96c..2e7de8d68 100644 --- a/lib/Echidna/Transaction.hs +++ b/lib/Echidna/Transaction.hs @@ -3,7 +3,9 @@ module Echidna.Transaction where -import Control.Lens +import Optics.Core +import Optics.State.Operators + import Control.Monad (join) import Control.Monad.Random.Strict (MonadRandom, getRandomR, uniform) import Control.Monad.State.Strict (MonadState, gets, modify') @@ -13,22 +15,24 @@ import Data.Maybe (mapMaybe) import Data.Set (Set) import Data.Set qualified as Set import Data.Vector qualified as V -import EVM hiding (resetState, tx, value) + +import EVM hiding (resetState, VMOpts(timestamp, gasprice)) import EVM.ABI (abiValueType) -import EVM.Types (Expr(ConcreteBuf, Lit), Addr, W256) +import EVM.Types (Expr(ConcreteBuf, Lit), Addr, W256, FunctionSelector) import Echidna.ABI import Echidna.Types.Random import Echidna.Orphans.JSON () import Echidna.Types (fromEVM) import Echidna.Types.Buffer (forceBuf, forceLit) -import Echidna.Types.Signature (SignatureMap, SolCall, ContractA, FunctionHash, MetadataCache, lookupBytecodeMetadata) +import Echidna.Types.Signature + (SignatureMap, SolCall, ContractA, MetadataCache, lookupBytecodeMetadata) import Echidna.Types.Tx import Echidna.Types.World (World(..)) import Echidna.Types.Campaign hasSelfdestructed :: VM -> Addr -> Bool -hasSelfdestructed vm addr = addr `elem` vm._tx._substate._selfdestructs +hasSelfdestructed vm addr = addr `elem` vm.tx.substate.selfdestructs -- | If half a tuple is zero, make both halves zero. Useful for generating -- delays, since block number only goes up with timestamp @@ -75,7 +79,7 @@ genTx memo world txConf deployedContracts = do where toContractA :: SignatureMap -> (Addr, Contract) -> Maybe ContractA toContractA sigMap (addr, c) = - let bc = forceBuf $ c ^. bytecode + let bc = forceBuf $ view bytecode c metadata = lookupBytecodeMetadata memo bc in (addr,) <$> Map.lookup metadata sigMap @@ -89,7 +93,7 @@ genValue :: MonadRandom m => W256 -> Set W256 - -> [FunctionHash] + -> [FunctionSelector] -> SolCall -> m W256 genValue mv ds ps sc = @@ -151,43 +155,43 @@ mutateTx tx = pure tx setupTx :: MonadState VM m => Tx -> m () setupTx tx@Tx{call = NoCall} = fromEVM $ do modify' $ \vm -> vm - { _state = resetState vm._state - , _block = advanceBlock vm._block tx.delay + { state = resetState vm.state + , block = advanceBlock vm.block tx.delay } loadContract tx.dst setupTx tx@Tx{call} = fromEVM $ do modify' $ \vm -> vm - { _result = Nothing - , _state = (resetState vm._state) - { _gas = tx.gas - , _caller = Lit (fromIntegral tx.src) - , _callvalue = Lit tx.value + { result = Nothing + , state = (resetState vm.state) + { gas = tx.gas + , caller = Lit (fromIntegral tx.src) + , callvalue = Lit tx.value } - , _block = advanceBlock vm._block tx.delay - , _tx = vm._tx { _gasprice = tx.gasprice, _origin = tx.src } + , block = advanceBlock vm.block tx.delay + , tx = vm.tx { gasprice = tx.gasprice, origin = tx.src } } case call of SolCreate bc -> do - env . contracts . at tx.dst .= Just (initialContract (InitCode bc mempty) & set balance tx.value) + #env % #contracts % at tx.dst .= Just (initialContract (InitCode bc mempty) & set #balance tx.value) loadContract tx.dst - state . code .= RuntimeCode (ConcreteRuntimeCode bc) + #state % #code .= RuntimeCode (ConcreteRuntimeCode bc) SolCall cd -> do incrementBalance loadContract tx.dst - state . calldata .= ConcreteBuf (encode cd) + #state % #calldata .= ConcreteBuf (encode cd) SolCalldata cd -> do incrementBalance loadContract tx.dst - state . calldata .= ConcreteBuf cd + #state % #calldata .= ConcreteBuf cd where - incrementBalance = env . contracts . ix tx.dst . balance += tx.value + incrementBalance = #env % #contracts % ix tx.dst % #balance %= (+ tx.value) encode (n, vs) = abiCalldata (encodeSig (n, abiValueType <$> vs)) $ V.fromList vs resetState :: FrameState -> FrameState -resetState s = s { _pc = 0, _stack = mempty, _memory = mempty } +resetState s = s { pc = 0, stack = mempty, memory = mempty } advanceBlock :: Block -> (W256, W256) -> Block advanceBlock blk (t,b) = - blk { _timestamp = Lit (forceLit blk._timestamp + t) - , _number = blk._number + b } + blk { timestamp = Lit (forceLit blk.timestamp + t) + , number = blk.number + b } diff --git a/lib/Echidna/Types/Signature.hs b/lib/Echidna/Types/Signature.hs index 862444e9c..6a420b678 100644 --- a/lib/Echidna/Types/Signature.hs +++ b/lib/Echidna/Types/Signature.hs @@ -9,7 +9,6 @@ import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict qualified as M import Data.Maybe (fromMaybe) import Data.Text (Text) -import GHC.Word (Word32) import EVM.ABI (AbiType, AbiValue) import EVM.Types (Addr) @@ -21,8 +20,6 @@ type ContractName = Text -- | Name of a function type FunctionName = Text -type FunctionHash = Word32 - -- | Represents the type of a Solidity function. -- A tuple for the name of the function and the 'AbiType's of any arguments it expects. type SolSignature = (FunctionName, [AbiType]) diff --git a/lib/Echidna/Types/World.hs b/lib/Echidna/Types/World.hs index bc9fad11c..8e438639a 100644 --- a/lib/Echidna/Types/World.hs +++ b/lib/Echidna/Types/World.hs @@ -2,9 +2,9 @@ module Echidna.Types.World where import Data.Set (Set) -import EVM.Types (Addr) +import EVM.Types (Addr, FunctionSelector) -import Echidna.Types.Signature (FunctionHash, SignatureMap) +import Echidna.Types.Signature (SignatureMap) import Echidna.Events (EventMap) -- | The world is composed by: @@ -16,6 +16,6 @@ data World = World { senders :: Set Addr , highSignatureMap :: SignatureMap , lowSignatureMap :: Maybe SignatureMap - , payableSigs :: [FunctionHash] + , payableSigs :: [FunctionSelector] , eventMap :: EventMap } diff --git a/package.yaml b/package.yaml index 09e89a169..84aa4d5c6 100644 --- a/package.yaml +++ b/package.yaml @@ -26,11 +26,12 @@ dependencies: - hashable - hevm - html-entities - - lens - ListLike - MonadRandom - mtl - optparse-applicative + - optics + - optics-core - process - random - rosezipper @@ -48,7 +49,6 @@ dependencies: - http-conduit - html-conduit - xml-conduit - - wreq language: GHC2021 @@ -57,6 +57,7 @@ default-extensions: - LambdaCase - MultiWayIf - NoFieldSelectors + - OverloadedLabels - OverloadedRecordDot - OverloadedStrings diff --git a/src/Main.hs b/src/Main.hs index 87b0ce04a..17bb02587 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -2,7 +2,8 @@ module Main where -import Control.Lens (view) +import Optics.Core (view) + import Control.Monad (unless, forM_, when) import Control.Monad.Reader (runReaderT) import Control.Monad.Random (getRandomR) diff --git a/src/test/Tests/Config.hs b/src/test/Tests/Config.hs index b29255ead..0e9a01581 100644 --- a/src/test/Tests/Config.hs +++ b/src/test/Tests/Config.hs @@ -3,7 +3,7 @@ module Tests.Config (configTests) where import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase, assertBool, assertFailure) -import Control.Lens (sans) +import Optics.Core (sans) import Control.Monad (void) import Data.Function ((&)) import Data.Yaml qualified as Y diff --git a/stack.yaml b/stack.yaml index 57f48d5dd..52635e2d2 100644 --- a/stack.yaml +++ b/stack.yaml @@ -5,7 +5,7 @@ packages: extra-deps: - git: https://github.com/ethereum/hevm.git - commit: fa2eb28fe9c793f9c5d03e95475baa8d8e0b5794 + commit: 1c43a07a692f4af08d91e2116880af9e44e464ec - restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 - s-cargot-0.1.4.0@sha256:61ea1833fbb4c80d93577144870e449d2007d311c34d74252850bb48aa8c31fb,3525