diff --git a/lib/Echidna/Config.hs b/lib/Echidna/Config.hs index 86b2441b..ac332f17 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 82ea7f7c..f791e503 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 84f82fec..64bae8b7 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 5165de76..8eb7e7cb 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 07a5ce7d..312c805d 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 957391f1..818156fa 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