Skip to content

Commit

Permalink
Read assert locations and determinate if they were executed or not
Browse files Browse the repository at this point in the history
Co-authored-by: ggrieco-tob <[email protected]>
  • Loading branch information
samalws-tob and ggrieco-tob committed Aug 20, 2024
1 parent 6956030 commit ea375a9
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 7 deletions.
4 changes: 2 additions & 2 deletions lib/Echidna.hs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ prepareContract
-> BuildOutput
-> Maybe ContractName
-> Seed
-> IO (VM Concrete RealWorld, Env, GenDict)
-> IO (VM Concrete RealWorld, Env, GenDict, AssertMappingByContract)
prepareContract cfg solFiles buildOutput selectedContract seed = do
let solConf = cfg.solConf
(Contracts contractMap) = buildOutput.contracts
Expand Down Expand Up @@ -90,7 +90,7 @@ prepareContract cfg solFiles buildOutput selectedContract seed = do
seed
(returnTypes contracts)

pure (vm, env, dict)
pure (vm, env, dict, slitherInfo.asserts)

loadInitialCorpus :: Env -> IO [(FilePath, [Tx])]
loadInitialCorpus env = do
Expand Down
25 changes: 25 additions & 0 deletions lib/Echidna/Output/Source.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Echidna.Output.Source where

import Prelude hiding (writeFile)

import Control.Monad (unless)
import Data.ByteString qualified as BS
import Data.Foldable
import Data.IORef (readIORef)
Expand Down Expand Up @@ -31,6 +32,7 @@ import Echidna.Types.Campaign (CampaignConf(..))
import Echidna.Types.Config (Env(..), EConfig(..))
import Echidna.Types.Coverage (OpIx, unpackTxResults, CoverageMap, CoverageFileType (..))
import Echidna.Types.Tx (TxResult(..))
import Echidna.SourceAnalysis.Slither (AssertMappingByContract, AssertLocation(..))

saveCoverages
:: Env
Expand Down Expand Up @@ -188,3 +190,26 @@ buildRuntimeLinesMap sc contracts =
where
srcMaps = concatMap
(\c -> toList $ c.runtimeSrcmap <> c.creationSrcmap) contracts

checkAssertionsCoverage
:: SourceCache
-> [SolcContract]
-> CoverageMap
-> AssertMappingByContract
-> IO ()
checkAssertionsCoverage sc cs covMap assertMap = do
covLines <- srcMapCov sc covMap cs
let asserts = concat $ concatMap Map.elems $ Map.elems assertMap
mapM_ (checkAssertionReached covLines) asserts

checkAssertionReached :: Map String (Map Int [TxResult]) -> AssertLocation -> IO ()
checkAssertionReached covLines assert =
maybe
warnAssertNotReached checkCoverage
(Map.lookup assert.filenameAbsolute covLines)
where
checkCoverage coverage = let lineNumbers = Map.keys coverage in
unless ((head assert.assertLines) `elem` lineNumbers) warnAssertNotReached
warnAssertNotReached =
putStrLn $ "WARNING: assertion at file: " ++ assert.filenameRelative
++ " starting at line: " ++ show (head assert.assertLines) ++ " was never reached"
5 changes: 4 additions & 1 deletion lib/Echidna/Solidity.hs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ mkWorld SolConf{sender, testMode} sigMap maybeContract slitherInfo contracts =
let
eventMap = Map.unions $ map (.eventMap) contracts
payableSigs = filterResults maybeContract slitherInfo.payableFunctions
as = if isAssertionMode testMode then filterResults maybeContract slitherInfo.asserts else []
as = if isAssertionMode testMode then filterResults maybeContract (getAssertFns <$> slitherInfo.asserts) else []
cs = if isDapptestMode testMode then [] else filterResults maybeContract slitherInfo.constantFunctions \\ as
(highSignatureMap, lowSignatureMap) = prepareHashMaps cs as $
filterFallbacks slitherInfo.fallbackDefined slitherInfo.receiveDefined contracts sigMap
Expand All @@ -352,6 +352,9 @@ mkWorld SolConf{sender, testMode} sigMap maybeContract slitherInfo contracts =
, payableSigs
, eventMap
}
where
getAssertFns :: Map FunctionName [AssertLocation] -> [FunctionName]
getAssertFns fnToAsserts = map fst $ filter (not . null . snd) $ Map.toList fnToAsserts

-- | This function is used to filter the lists of function names according to the supplied
-- contract name (if any) and returns a list of hashes
Expand Down
23 changes: 22 additions & 1 deletion lib/Echidna/SourceAnalysis/Slither.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,32 @@ enhanceConstants si =
enh (AbiString s) = makeArrayAbiValues s
enh v = [v]

data AssertLocation = AssertLocation
{ start :: Int
, filenameRelative :: String
, filenameAbsolute :: String
, assertLines :: [Int]
, startColumn :: Int
, endingColumn :: Int
} deriving (Show)

type AssertMappingByContract = Map ContractName (Map FunctionName [AssertLocation])

instance FromJSON AssertLocation where
parseJSON = withObject "" $ \o -> do
start <- o.: "start"
filenameRelative <- o.: "filename_relative"
filenameAbsolute <- o.: "filename_absolute"
assertLines <- o.: "lines"
startColumn <- o.: "starting_column"
endingColumn <- o.: "ending_column"
pure AssertLocation {..}

-- we loose info on what constants are in which functions
data SlitherInfo = SlitherInfo
{ payableFunctions :: Map ContractName [FunctionName]
, constantFunctions :: Map ContractName [FunctionName]
, asserts :: Map ContractName [FunctionName]
, asserts :: AssertMappingByContract
, constantValues :: Map ContractName (Map FunctionName [AbiValue])
, generationGraph :: Map ContractName (Map FunctionName [FunctionName])
, solcVersions :: [Version]
Expand Down
7 changes: 5 additions & 2 deletions src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,18 @@ main = withUtf8 $ withCP65001 $ do

-- take the seed from config, otherwise generate a new one
seed <- maybe (getRandomR (0, maxBound)) pure cfg.campaignConf.seed
(vm, env, dict) <- prepareContract cfg cliFilePath buildOutput cliSelectedContract seed
(vm, env, dict, asserts) <- prepareContract cfg cliFilePath buildOutput cliSelectedContract seed

initialCorpus <- loadInitialCorpus env
-- start ui and run tests
_campaign <- runReaderT (ui vm dict initialCorpus cliSelectedContract) env

tests <- traverse readIORef env.testRefs

let contracts = Map.elems env.dapp.solcByName
coverage <- readIORef env.coverageRef
checkAssertionsCoverage buildOutput.sources contracts coverage asserts

Onchain.saveRpcCache env

-- save corpus
Expand Down Expand Up @@ -108,7 +112,6 @@ main = withUtf8 $ withCP65001 $ do
Onchain.saveCoverageReport env runId

-- save source coverage reports
let contracts = Map.elems env.dapp.solcByName
saveCoverages env runId dir buildOutput.sources contracts

if isSuccessful tests then exitSuccess else exitWith (ExitFailure 1)
Expand Down
2 changes: 1 addition & 1 deletion src/test/Common.hs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ runContract f selectedContract cfg workerType = do
seed <- maybe (getRandomR (0, maxBound)) pure cfg.campaignConf.seed
buildOutput <- compileContracts cfg.solConf (f :| [])

(vm, env, dict) <- prepareContract cfg (f :| []) buildOutput selectedContract seed
(vm, env, dict, _) <- prepareContract cfg (f :| []) buildOutput selectedContract seed

(_stopReason, finalState) <- flip runReaderT env $
runWorker workerType (pure ()) vm dict 0 [] cfg.campaignConf.testLimit selectedContract
Expand Down

0 comments on commit ea375a9

Please sign in to comment.