Skip to content

Commit

Permalink
prototype of the code to detect when assertions are not reached
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrieco-tob committed Sep 1, 2023
1 parent 08bb39b commit 7faacf4
Show file tree
Hide file tree
Showing 4 changed files with 34 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 @@ -45,7 +45,7 @@ prepareContract
-> NonEmpty FilePath
-> Maybe ContractName
-> Seed
-> IO (VM, World, GenDict)
-> IO (VM, World, GenDict, AssertMappingByContract)
prepareContract env contracts solFiles specifiedContract seed = do
let solConf = env.cfg.solConf

Expand Down Expand Up @@ -85,7 +85,7 @@ prepareContract env contracts solFiles specifiedContract seed = do
(returnTypes contracts)

writeIORef env.testsRef echidnaTests
pure (vm, world, dict)
pure (vm, world, dict, slitherInfo.asserts)

loadInitialCorpus :: Env -> World -> IO [[Tx]]
loadInitialCorpus env world = 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.Aeson (ToJSON(..), FromJSON(..), withText)
import Data.ByteString qualified as BS
import Data.Foldable
Expand All @@ -30,6 +31,7 @@ import EVM.Solidity (SourceCache(..), SrcMap, SolcContract(..))
import Echidna.Types.Coverage (OpIx, unpackTxResults, CoverageMap)
import Echidna.Types.Tx (TxResult(..))
import Echidna.Types.Signature (getBytecodeMetadata)
import Echidna.Processor (AssertMappingByContract, AssertLocation(..))

saveCoverages
:: [CoverageFileType]
Expand Down Expand Up @@ -199,3 +201,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"
6 changes: 4 additions & 2 deletions lib/Echidna/Processor.hs
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,23 @@ data AssertLocation = AssertLocation
, 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.: "start_column"
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 (Map FunctionName [AssertLocation])
, asserts :: AssertMappingByContract
, constantValues :: Map ContractName (Map FunctionName [AbiValue])
, generationGraph :: Map ContractName (Map FunctionName [FunctionName])
, solcVersions :: [Version]
Expand Down
6 changes: 3 additions & 3 deletions src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ main = withUtf8 $ withCP65001 $ do

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

initialCorpus <- loadInitialCorpus env world
Expand All @@ -125,6 +125,8 @@ main = withUtf8 $ withCP65001 $ do
slotsCache <- readIORef cacheSlotsRef

tests <- readIORef testsRef
coverage <- readIORef coverageRef
checkAssertionsCoverage buildOutput.sources contracts coverage asserts

-- save corpus
case cfg.campaignConf.corpusDir of
Expand Down Expand Up @@ -156,8 +158,6 @@ main = withUtf8 $ withCP65001 $ do
-- as it orders the runs chronologically.
runId <- fromIntegral . systemSeconds <$> getSystemTime

coverage <- readIORef coverageRef

-- coverage reports for external contracts, we only support
-- Ethereum Mainnet for now
when (chainId == Just 1) $ do
Expand Down

0 comments on commit 7faacf4

Please sign in to comment.