Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Direct foundry compilation #1161

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
foundry.url = "github:shazow/foundry.nix/monthly";
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
Expand All @@ -12,7 +13,7 @@
};
};

outputs = { self, nixpkgs, flake-utils, nix-bundle-exe, ... }:
outputs = { self, nixpkgs, flake-utils, foundry, nix-bundle-exe, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
Expand Down Expand Up @@ -149,6 +150,7 @@
buildInputs = [
solc
slither-analyzer
foundry.defaultPackage.${system}
haskellPackages.hlint
haskellPackages.cabal-install
haskellPackages.haskell-language-server
Expand Down
63 changes: 47 additions & 16 deletions lib/Echidna/Solidity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import Data.Text (Text, isPrefixOf, isSuffixOf, append)
import Data.Text qualified as T
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.IO (openFile, IOMode(..))
import System.Info (os)
import System.Process

import EVM (initialContract, currentContract)
import EVM.ABI
Expand Down Expand Up @@ -85,42 +85,73 @@ readSolcBatch d = do
-- | Given a list of files, use its extenstion to check if it is a precompiled
-- contract or try to compile it and get a list of its contracts and a list of source
-- cache, throwing exceptions if necessary.
compileContracts
:: SolConf
-> NonEmpty FilePath
-> IO [BuildOutput]
compileContracts solConf fp = do
path <- findExecutable "crytic-compile" >>= \case
compileContracts :: SolConf -> NonEmpty FilePath -> IO [BuildOutput]
compileContracts solConf targetPaths = do
case targetPaths of
targetPath :| [] ->
doesFileExist (targetPath </> "foundry.toml") >>= \case
True -> do
buildWithFoundry solConf targetPath >>= \case
Nothing -> buildWithCryticCompile solConf targetPaths
Just buildOutput -> pure [buildOutput]
False -> buildWithCryticCompile solConf targetPaths
_ -> buildWithCryticCompile solConf targetPaths

buildWithFoundry :: SolConf -> FilePath -> IO (Maybe BuildOutput)
buildWithFoundry solConf projectPath = do
findExecutable "forge" >>= \case
Nothing -> pure Nothing
Just forge -> do
unless solConf.quiet $ putStrLn "Foundry project detected, running forge."
stream <- if solConf.quiet
then UseHandle <$> openFile nullFilePath WriteMode
else pure Inherit
let processParams = (proc forge ["build"])
{ cwd = Just projectPath
, std_out = stream
, std_err = stream
}
(_, _, _, processHandle) <- createProcess processParams
waitForProcess processHandle >>= \case
ExitFailure code ->
throwM $ CompileFailure ("forge failed with error code: " <> show code) ""
ExitSuccess ->
readBuildOutput projectPath Foundry >>= \case
Right buildOutput -> pure (Just buildOutput)
Left err ->
throwM $ CompileFailure ("reading forge build output failed with error:\n" <> err) ""

buildWithCryticCompile :: SolConf -> NonEmpty FilePath -> IO [BuildOutput]
buildWithCryticCompile solConf targetPaths = do
cryticCompile <- findExecutable "crytic-compile" >>= \case
Nothing -> throwM NoCryticCompile
Just path -> pure path
Just cryticCompile -> pure cryticCompile

let
usual = ["--solc-disable-warnings", "--export-format", "solc"]
solargs = solConf.solcArgs ++ linkLibraries solConf.solcLibs & (usual ++) .
(\sa -> if null sa then [] else ["--solc-args", sa])
compileOne :: FilePath -> IO [BuildOutput]
compileOne x = do
stderr <- if solConf.quiet
then UseHandle <$> openFile nullFilePath WriteMode
else pure Inherit
(ec, out, err) <- measureIO solConf.quiet ("Compiling " <> x) $ do
readCreateProcessWithExitCode
(proc path $ (solConf.cryticArgs ++ solargs) |> x) {std_err = stderr} ""
(proc cryticCompile $ (solConf.cryticArgs ++ solargs) |> x) ""
case ec of
ExitSuccess -> readSolcBatch "crytic-export"
ExitFailure _ -> throwM $ CompileFailure out err

-- | OS-specific path to the "null" file, which accepts writes without storing them
nullFilePath :: String
nullFilePath = if os == "mingw32" then "\\\\.\\NUL" else "/dev/null"
-- clean up previous artifacts
removeJsonFiles "crytic-export"
buildOutputs <- mapM compileOne fp
buildOutputs <- mapM compileOne targetPaths
when (length buildOutputs > 1) $
putStrLn "WARNING: more than one SourceCaches was found after compile. \
\Only the first one will be used."
pure $ NE.head buildOutputs

-- | OS-specific path to the "null" file, which accepts writes without storing them
nullFilePath :: String
nullFilePath = if os == "mingw32" then "\\\\.\\NUL" else "/dev/null"

removeJsonFiles :: FilePath -> IO ()
removeJsonFiles dir =
whenM (doesDirectoryExist dir) $ do
Expand Down
Loading