1- {-# LANGUAGE DeriveAnyClass #-}
2-
3- module Echidna.Onchain where
1+ module Echidna.Onchain
2+ ( etherscanApiKey
3+ , fetchChainIdFrom
4+ , rpcBlockEnv
5+ , rpcUrlEnv
6+ , safeFetchContractFrom
7+ , safeFetchSlotFrom
8+ , saveCoverageReport
9+ , saveRpcCache
10+ )
11+ where
412
513import Control.Concurrent.MVar (readMVar )
614import Control.Exception (catch )
715import Control.Monad (when , forM_ )
8- import Data.Aeson (ToJSON , FromJSON )
9- import Data.ByteString (ByteString )
1016import Data.ByteString qualified as BS
1117import Data.ByteString.UTF8 qualified as UTF8
1218import Data.Map qualified as Map
@@ -16,23 +22,21 @@ import Data.Text qualified as Text
1622import Data.Vector qualified as Vector
1723import Data.Word (Word64 )
1824import Etherscan qualified
19- import GHC.Generics (Generic )
2025import Network.HTTP.Simple (HttpException )
2126import Network.Wreq.Session qualified as Session
2227import Optics (view )
23- import System.Directory (doesFileExist )
2428import System.Environment (lookupEnv )
2529import System.FilePath ((</>) )
2630import Text.Read (readMaybe )
2731
28- import EVM (initialContract , bytecode )
32+ import EVM (bytecode )
2933import EVM.Effects (defaultConfig )
3034import EVM.Fetch qualified
3135import EVM.Solidity (SourceCache (.. ), SolcContract (.. ))
3236import EVM.Types hiding (Env )
3337
3438import Echidna.Output.Source (saveCoverages )
35- import Echidna.SymExec.Symbolic (forceWord , forceBuf )
39+ import Echidna.SymExec.Symbolic (forceBuf )
3640import Echidna.Types.Campaign (CampaignConf (.. ))
3741import Echidna.Types.Config (Env (.. ), EConfig (.. ))
3842
@@ -79,99 +83,69 @@ safeFetchSlotFrom session rpcBlock rpcUrl addr slot =
7983 (EVM.Fetch. fetchSlotWithCache defaultConfig session rpcBlock rpcUrl addr slot)
8084 (\ (e :: HttpException ) -> pure $ EVM.Fetch. FetchError (Text. pack $ show e))
8185
82- data FetchedContractData = FetchedContractData
83- { runtimeCode :: ByteString
84- , nonce :: Maybe W64
85- , balance :: W256
86- }
87- deriving (Generic , ToJSON , FromJSON , Show )
88-
89- fromFetchedContractData :: FetchedContractData -> Contract
90- fromFetchedContractData contractData =
91- (initialContract (RuntimeCode (ConcreteRuntimeCode contractData. runtimeCode)))
92- { nonce = contractData. nonce
93- , balance = Lit contractData. balance
94- , external = True
95- }
96-
97- toFetchedContractData :: Contract -> FetchedContractData
98- toFetchedContractData contract =
99- let code = case contract. code of
100- RuntimeCode (ConcreteRuntimeCode c) -> c
101- _ -> error " unexpected code"
102- in FetchedContractData
103- { runtimeCode = code
104- , nonce = contract. nonce
105- , balance = forceWord contract. balance
106- }
107-
108-
109- readFileIfExists :: FilePath -> IO (Maybe BS. ByteString )
110- readFileIfExists path = do
111- exists <- doesFileExist path
112- if exists then Just <$> BS. readFile path else pure Nothing
113-
11486-- | "Reverse engineer" the SolcContract and SourceCache structures for the
11587-- code fetched from the outside
116- externalSolcContract :: Env -> Addr -> Contract -> IO (Maybe (SourceCache , SolcContract ))
117- externalSolcContract env addr c = do
118- let runtimeCode = forceBuf $ fromJust $ view bytecode c
119- putStr $ " Fetching Solidity source for contract at address " <> show addr <> " ... "
120- srcRet <- Etherscan. fetchContractSource env. cfg. etherscanApiKey addr
121- putStrLn $ if isJust srcRet then " Success!" else " Error!"
122- putStr $ " Fetching Solidity source map for contract at address " <> show addr <> " ... "
123- srcmapRet <- Etherscan. fetchContractSourceMap addr
124- putStrLn $ if isJust srcmapRet then " Success!" else " Error!"
125- pure $ do
126- src <- srcRet
127- (_, srcmap) <- srcmapRet
128- let
129- files = Map. singleton 0 (show addr, UTF8. fromString src. code)
130- sourceCache = SourceCache
131- { files
132- , lines = Vector. fromList . BS. split 0xa . snd <$> files
133- , asts = mempty
134- }
135- solcContract = SolcContract
136- { runtimeCode = runtimeCode
137- , creationCode = mempty
138- , runtimeCodehash = keccak' runtimeCode
139- , creationCodehash = keccak' mempty
140- , runtimeSrcmap = mempty
141- , creationSrcmap = srcmap
142- , contractName = src. name
143- , constructorInputs = [] -- error "TODO: mkConstructor abis TODO"
144- , abiMap = mempty -- error "TODO: mkAbiMap abis"
145- , eventMap = mempty -- error "TODO: mkEventMap abis"
146- , errorMap = mempty -- error "TODO: mkErrorMap abis"
147- , storageLayout = Nothing
148- , immutableReferences = mempty
149- }
150- pure (sourceCache, solcContract)
88+ externalSolcContract :: Env -> String -> Addr -> Contract -> IO (Maybe (SourceCache , SolcContract ))
89+ externalSolcContract env explorerUrl addr c = do
90+ case env. cfg. etherscanApiKey of
91+ Nothing -> pure Nothing
92+ Just _ -> do
93+ let runtimeCode = forceBuf $ fromJust $ view bytecode c
94+ putStr $ " Fetching Solidity source for contract at address " <> show addr <> " ... "
95+ srcRet <- Etherscan. fetchContractSource env. chainId env. cfg. etherscanApiKey addr
96+ putStrLn $ if isJust srcRet then " Success!" else " Error!"
97+ putStr $ " Fetching Solidity source map for contract at address " <> show addr <> " ... "
98+ srcmapRet <- Etherscan. fetchContractSourceMap explorerUrl addr
99+ putStrLn $ if isJust srcmapRet then " Success!" else " Error!"
100+ pure $ do
101+ src <- srcRet
102+ (_, srcmap) <- srcmapRet
103+ let
104+ files = Map. singleton 0 (show addr, UTF8. fromString src. code)
105+ sourceCache = SourceCache
106+ { files
107+ , lines = Vector. fromList . BS. split 0xa . snd <$> files
108+ , asts = mempty
109+ }
110+ solcContract = SolcContract
111+ { runtimeCode = runtimeCode
112+ , creationCode = mempty
113+ , runtimeCodehash = keccak' runtimeCode
114+ , creationCodehash = keccak' mempty
115+ , runtimeSrcmap = mempty
116+ , creationSrcmap = srcmap
117+ , contractName = src. name
118+ , constructorInputs = [] -- error "TODO: mkConstructor abis TODO"
119+ , abiMap = mempty -- error "TODO: mkAbiMap abis"
120+ , eventMap = mempty -- error "TODO: mkEventMap abis"
121+ , errorMap = mempty -- error "TODO: mkErrorMap abis"
122+ , storageLayout = Nothing
123+ , immutableReferences = mempty
124+ }
125+ pure (sourceCache, solcContract)
151126
152127
153128saveCoverageReport :: Env -> Int -> IO ()
154129saveCoverageReport env runId = do
155130 case env. cfg. campaignConf. corpusDir of
156131 Nothing -> pure ()
157132 Just dir -> do
158- -- coverage reports for external contracts, we only support
159- -- Ethereum Mainnet for now
160- when (env. chainId == Just 1 ) $ do
161- -- Get contracts from hevm session cache
162- sessionCache <- readMVar env. fetchSession. sharedCache
163- let contractsCache = EVM.Fetch. makeContractFromRPC <$> sessionCache. contractCache
164- forM_ (Map. toList contractsCache) $ \ (addr, contract) -> do
165- r <- externalSolcContract env addr contract
166- case r of
167- Just (externalSourceCache, solcContract) -> do
168- let dir' = dir </> show addr
169- saveCoverages env
170- runId
171- dir'
172- externalSourceCache
173- [solcContract]
174- Nothing -> pure ()
133+ -- coverage reports for external contracts
134+ -- Get contracts from hevm session cache
135+ sessionCache <- readMVar env. fetchSession. sharedCache
136+ explorerUrl <- Etherscan. getBlockExplorerUrl env. chainId
137+ let contractsCache = EVM.Fetch. makeContractFromRPC <$> sessionCache. contractCache
138+ forM_ (Map. toList contractsCache) $ \ (addr, contract) -> do
139+ r <- externalSolcContract env explorerUrl addr contract
140+ case r of
141+ Just (externalSourceCache, solcContract) -> do
142+ let dir' = dir </> show addr
143+ saveCoverages env
144+ runId
145+ dir'
146+ externalSourceCache
147+ [solcContract]
148+ Nothing -> pure ()
175149
176150fetchChainIdFrom :: Maybe Text -> IO (Maybe W256 )
177151fetchChainIdFrom (Just url) = do
0 commit comments