From 7ff9173b9fc740ab322d9617069146d1e8842b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= <2642849+elopez@users.noreply.github.com> Date: Mon, 6 May 2024 06:06:48 -0300 Subject: [PATCH] Support configuring Etherscan API key via config file (#1227) Partially fixes #1226 --- lib/Echidna/Config.hs | 1 + lib/Echidna/Onchain.hs | 13 +++++++++---- lib/Echidna/Types/Config.hs | 1 + lib/Etherscan.hs | 8 +++----- src/Main.hs | 2 ++ tests/solidity/basic/default.yaml | 2 ++ 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/Echidna/Config.hs b/lib/Echidna/Config.hs index 86b2441b9..ac332f17d 100644 --- a/lib/Echidna/Config.hs +++ b/lib/Echidna/Config.hs @@ -56,6 +56,7 @@ instance FromJSON EConfigWithUsage where <*> (UIConf <$> v ..:? "timeout" <*> formatParser) <*> v ..:? "rpcUrl" <*> v ..:? "rpcBlock" + <*> v ..:? "etherscanApiKey" where useKey k = modify' $ Set.insert k x ..:? k = useKey k >> lift (x .:? k) diff --git a/lib/Echidna/Onchain.hs b/lib/Echidna/Onchain.hs index 82ea7f7c7..f791e5039 100644 --- a/lib/Echidna/Onchain.hs +++ b/lib/Echidna/Onchain.hs @@ -50,6 +50,11 @@ rpcBlockEnv = do val <- lookupEnv "ECHIDNA_RPC_BLOCK" pure (val >>= readMaybe) +etherscanApiKey :: IO (Maybe Text) +etherscanApiKey = do + val <- lookupEnv "ETHERSCAN_API_KEY" + pure (Text.pack <$> val) + -- TODO: temporary solution, handle errors gracefully safeFetchContractFrom :: EVM.Fetch.BlockNumber -> Text -> Addr -> IO (Maybe Contract) safeFetchContractFrom rpcBlock rpcUrl addr = @@ -122,11 +127,11 @@ readFileIfExists path = do -- | "Reverse engineer" the SolcContract and SourceCache structures for the -- code fetched from the outside -externalSolcContract :: Addr -> Contract -> IO (Maybe (SourceCache, SolcContract)) -externalSolcContract addr c = do +externalSolcContract :: Env -> Addr -> Contract -> IO (Maybe (SourceCache, SolcContract)) +externalSolcContract env addr c = do let runtimeCode = forceBuf $ fromJust $ view bytecode c putStr $ "Fetching Solidity source for contract at address " <> show addr <> "... " - srcRet <- Etherscan.fetchContractSource addr + srcRet <- Etherscan.fetchContractSource env.cfg.etherscanApiKey addr putStrLn $ if isJust srcRet then "Success!" else "Error!" putStr $ "Fetching Solidity source map for contract at address " <> show addr <> "... " srcmapRet <- Etherscan.fetchContractSourceMap addr @@ -189,7 +194,7 @@ saveCoverageReport env runId = do forM_ (Map.toList contractsCache) $ \(addr, mc) -> case mc of Just contract -> do - r <- externalSolcContract addr contract + r <- externalSolcContract env addr contract case r of Just (externalSourceCache, solcContract) -> do let dir' = dir show addr diff --git a/lib/Echidna/Types/Config.hs b/lib/Echidna/Types/Config.hs index 84f82fecb..64bae8b79 100644 --- a/lib/Echidna/Types/Config.hs +++ b/lib/Echidna/Types/Config.hs @@ -43,6 +43,7 @@ data EConfig = EConfig , rpcUrl :: Maybe Text , rpcBlock :: Maybe Word64 + , etherscanApiKey :: Maybe Text } instance Read OutputFormat where diff --git a/lib/Etherscan.hs b/lib/Etherscan.hs index 5165de76c..8eb7e7cb3 100644 --- a/lib/Etherscan.hs +++ b/lib/Etherscan.hs @@ -10,7 +10,6 @@ import Data.Sequence (Seq) import Data.Text (Text) import Data.Text qualified as T import Network.HTTP.Simple (httpSink, parseRequest, getResponseBody, httpJSON) -import System.Environment (lookupEnv) import Text.HTML.DOM (sinkDoc) import Text.XML.Cursor (attributeIs, content, element, fromDocument, ($//), (&//)) @@ -23,14 +22,13 @@ data SourceCode = SourceCode } deriving Show -fetchContractSource :: Addr -> IO (Maybe SourceCode) -fetchContractSource addr = do - apiKey <- lookupEnv "ETHERSCAN_API_KEY" +fetchContractSource :: Maybe Text -> Addr -> IO (Maybe SourceCode) +fetchContractSource apiKey addr = do url <- parseRequest $ "https://api.etherscan.io/api?" <> "module=contract" <> "&action=getsourcecode" <> "&address=" <> show addr - <> maybe "" ("&apikey=" <>) apiKey + <> T.unpack (maybe "" ("&apikey=" <>) apiKey) try url (5 :: Int) where try url n = do diff --git a/src/Main.hs b/src/Main.hs index 07a5ce7df..312c805d3 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -236,12 +236,14 @@ overrideConfig :: EConfig -> Options -> IO EConfig overrideConfig config Options{..} = do envRpcUrl <- Onchain.rpcUrlEnv envRpcBlock <- Onchain.rpcBlockEnv + envEtherscanApiKey <- Onchain.etherscanApiKey pure $ config { solConf = overrideSolConf config.solConf , campaignConf = overrideCampaignConf config.campaignConf , uiConf = overrideUiConf config.uiConf , rpcUrl = cliRpcUrl <|> envRpcUrl <|> config.rpcUrl , rpcBlock = cliRpcBlock <|> envRpcBlock <|> config.rpcBlock + , etherscanApiKey = envEtherscanApiKey <|> config.etherscanApiKey } & overrideFormat where diff --git a/tests/solidity/basic/default.yaml b/tests/solidity/basic/default.yaml index 957391f13..818156fa6 100644 --- a/tests/solidity/basic/default.yaml +++ b/tests/solidity/basic/default.yaml @@ -87,6 +87,8 @@ maxValue: 100000000000000000000 # 100 eth rpcUrl: null # block number to use when fetching over RPC rpcBlock: null +# Etherscan API key +etherscanApiKey: null # number of workers workers: 1 # events server port