hevm-0.53.0: test/EVM/Test/Utils.hs
{-# LANGUAGE QuasiQuotes #-}
module EVM.Test.Utils where
import Data.String.Here
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.IO qualified as T
import GHC.IO.Handle (hClose)
import GHC.Natural
import Paths_hevm qualified as Paths
import System.Directory
import System.IO.Temp
import System.Process
import System.Exit
import EVM.Dapp (dappInfo, emptyDapp)
import EVM.Fetch (RpcInfo)
import EVM.Solidity
import EVM.Solvers
import EVM.UnitTest
import Control.Monad.ST (RealWorld)
import Control.Monad.IO.Unlift
import Control.Monad.Catch (MonadMask)
import EVM.Effects
import Data.Maybe (fromMaybe)
import EVM.Types (internalError)
import System.Environment (lookupEnv)
runSolidityTestCustom
:: (MonadMask m, App m)
=> FilePath -> Text -> Maybe Natural -> Maybe Integer -> Bool -> RpcInfo -> ProjectType -> m Bool
runSolidityTestCustom testFile match timeout maxIter ffiAllowed rpcinfo projectType = do
withSystemTempDirectory "dapp-test" $ \root -> do
(liftIO $ compile projectType root testFile) >>= \case
Left e -> error e
Right bo@(BuildOutput contracts _) -> do
withSolvers Z3 1 timeout $ \solvers -> do
opts <- liftIO $ testOpts solvers root (Just bo) match maxIter ffiAllowed rpcinfo
unitTest opts contracts
runSolidityTest
:: (MonadMask m, App m)
=> FilePath -> Text -> m Bool
runSolidityTest testFile match = runSolidityTestCustom testFile match Nothing Nothing True Nothing Foundry
testOpts :: SolverGroup -> FilePath -> Maybe BuildOutput -> Text -> Maybe Integer -> Bool -> RpcInfo -> IO (UnitTestOptions RealWorld)
testOpts solvers root buildOutput match maxIter allowFFI rpcinfo = do
let srcInfo = maybe emptyDapp (dappInfo root) buildOutput
params <- paramsFromRpc rpcinfo
pure UnitTestOptions
{ solvers = solvers
, rpcInfo = rpcinfo
, maxIter = maxIter
, askSmtIters = 1
, smtTimeout = Nothing
, solver = Nothing
, verbose = Just 1
, match = match
, testParams = params
, dapp = srcInfo
, ffiAllowed = allowFFI
}
compile :: ProjectType -> FilePath -> FilePath -> IO (Either String BuildOutput)
compile DappTools root src = do
json <- compileWithDSTest src
createDirectory (root <> "/out")
T.writeFile (root <> "/out/dapp.sol.json") json
readBuildOutput root DappTools
compile CombinedJSON _root _src = error "unsupported"
compile foundryType root src = do
createDirectory (root <> "/src")
writeFile (root <> "/src/unit-tests.t.sol") =<< readFile =<< Paths.getDataFileName src
initLib (root <> "/lib/ds-test") "test/contracts/lib/test.sol" "test.sol"
initLib (root <> "/lib/tokens") "test/contracts/lib/erc20.sol" "erc20.sol"
case foundryType of
FoundryStdLib -> initStdForgeDir (root <> "/lib/forge-std")
Foundry -> pure ()
r@(res,_,_) <- readProcessWithExitCode "forge" ["build", "--root", root] ""
case res of
ExitFailure _ -> pure . Left $ "compilation failed: " <> show r
ExitSuccess -> readBuildOutput root Foundry
where
initStdForgeDir :: FilePath -> IO ()
initStdForgeDir tld = do
createDirectoryIfMissing True tld
forgeStdRepo <- liftIO $ fromMaybe (internalError "cannot find forge-std repo") <$> (lookupEnv "HEVM_FORGE_STD_REPO")
callProcess "mkdir" ["-p", tld]
callProcess "cp" ["-r", forgeStdRepo <> "/src", tld <> "/src"]
callProcess "cp" ["-r", forgeStdRepo <> "/lib", tld <> "/lib"]
initLib :: FilePath -> FilePath -> FilePath -> IO ()
initLib tld srcFile dstFile = do
createDirectoryIfMissing True (tld <> "/src")
writeFile (tld <> "/src/" <> dstFile) =<< readFile =<< Paths.getDataFileName srcFile
_ <- readProcessWithExitCode "git" ["init", tld] ""
callProcess "git" ["config", "--file", tld <> "/.git/config", "user.name", "'hevm'"]
callProcess "git" ["config", "--file", tld <> "/.git/config", "user.email", "'hevm@hevm.dev'"]
callProcess "git" ["--git-dir", tld <> "/.git", "--work-tree", tld, "add", tld]
_ <- readProcessWithExitCode "git" ["--git-dir", tld <> "/.git", "--work-tree", tld, "--no-gpg-sign", "commit", "-m"] ""
pure ()
-- We don't want to depend on dapptools here, so we cheat and just call solc with the same options that dapp itself uses
compileWithDSTest :: FilePath -> IO Text
compileWithDSTest src =
withSystemTempFile "input.json" $ \file handle -> do
hClose handle
dsTest <- readFile =<< Paths.getDataFileName "test/contracts/lib/test.sol"
erc20 <- readFile =<< Paths.getDataFileName "test/contracts/lib/erc20.sol"
testFilePath <- Paths.getDataFileName src
testFile <- readFile testFilePath
T.writeFile file
[i|
{
"language": "Solidity",
"sources": {
"ds-test/test.sol": {
"content": ${dsTest}
},
"tokens/erc20.sol": {
"content": ${erc20}
},
"unit-tests.sol": {
"content": ${testFile}
}
},
"settings": {
"metadata": {
"useLiteralContent": true
},
"outputSelection": {
"*": {
"*": [
"metadata",
"evm.bytecode",
"evm.deployedBytecode",
"abi",
"storageLayout",
"evm.bytecode.sourceMap",
"evm.bytecode.linkReferences",
"evm.bytecode.generatedSources",
"evm.deployedBytecode.sourceMap",
"evm.deployedBytecode.linkReferences",
"evm.deployedBytecode.generatedSources"
],
"": [
"ast"
]
}
}
}
}
|]
x <- T.pack <$>
readProcess
"solc"
["--allow-paths", file, "--standard-json", file]
""
return x