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

Coverage Guided / Mutation Based Fuzzing #687

Draft
wants to merge 21 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
hevm: allow mutations percentage to be set from the command line
  • Loading branch information
d-xo committed May 27, 2021
commit ace7bf6fe9250ef2d12b2b2bd362f1f6cea1e913
8 changes: 7 additions & 1 deletion src/hevm/hevm-cli/hevm-cli.hs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ data Command w
, debug :: w ::: Bool <?> "Run interactively"
, jsontrace :: w ::: Bool <?> "Print json trace output at every step"
, fuzzRuns :: w ::: Maybe Int <?> "Number of times to run fuzz tests"
, mutations :: w ::: Maybe Int <?> "Percentage of fuzz runs that should mutate a previous input vs randomly generating new inputs"
, replay :: w ::: Maybe (Text, ByteString) <?> "Custom fuzz case to run/debug"
, rpc :: w ::: Maybe URL <?> "Fetch state from a remote node"
, verbose :: w ::: Maybe Int <?> "Append call trace: {1} failures {2} all"
Expand Down Expand Up @@ -291,6 +292,11 @@ unitTestOptions cmd testFile = do
, EVM.UnitTest.vmModifier = vmModifier
, EVM.UnitTest.testParams = params
, EVM.UnitTest.dapp = srcInfo
, EVM.UnitTest.mutations = case mutations cmd of
Nothing -> 50
Just x -> if x > 100
then error "Mutations cannot be greater than 100"
else x
}

main :: IO ()
Expand Down Expand Up @@ -757,7 +763,7 @@ vmFromCommand cmd = do
, EVM.vmoptChainId = word chainid 1
, EVM.vmoptCreate = create cmd
, EVM.vmoptStorageModel = ConcreteS
, EVM.vmoptTxAccessList = mempty -- TODO: support me soon
, EVM.vmoptTxAccessList = mempty -- TODO: support me soon
}
word f def = fromMaybe def (f cmd)
addr f def = fromMaybe def (f cmd)
Expand Down
10 changes: 2 additions & 8 deletions src/hevm/src/EVM/Mutate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,8 @@ import qualified Data.ListLike as LL

-- | Given an 'AbiValue', generate a random \"similar\" value of the same 'AbiType'.
mutateAbiValue :: AbiValue -> Gen AbiValue
mutateAbiValue (AbiUInt n x) = chooseInt (0, 9) >>= -- 10% of chance of mutation
\case
0 -> fixAbiUInt n <$> mutateNum x
_ -> return $ AbiUInt n x
mutateAbiValue (AbiInt n x) = chooseInt (0, 9) >>= -- 10% of chance of mutation
\case
0 -> fixAbiInt n <$> mutateNum x
_ -> return $ AbiInt n x
mutateAbiValue (AbiUInt n x) = frequency [(1, fixAbiUInt n <$> mutateNum x), (9, pure $ AbiUInt n x)]
mutateAbiValue (AbiInt n x) = frequency [(1, fixAbiInt n <$> mutateNum x), (9, pure $ AbiInt n x)]

mutateAbiValue (AbiAddress x) = return $ AbiAddress x
mutateAbiValue (AbiBool _) = genAbiValue AbiBoolType
Expand Down
19 changes: 8 additions & 11 deletions src/hevm/src/EVM/UnitTest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ data UnitTestOptions = UnitTestOptions
, vmModifier :: VM -> VM
, dapp :: DappInfo
, testParams :: TestVMParams
, mutations :: Int
}

data TestVMParams = TestVMParams
Expand All @@ -103,6 +104,7 @@ data TestVMParams = TestVMParams
}

type Corpus = Map (MultiSet OpLocation) (SolcContract, Text, AbiValue)
data FuzzResult = Pass | Fail VM String

defaultGasForCreating :: W256
defaultGasForCreating = 0xffffffffffff
Expand Down Expand Up @@ -196,15 +198,12 @@ checkFailures UnitTestOptions { .. } method bailed = do

-- | Either generates the calldata by mutating an example from the corpus, or synthesizes a random example
genWithCorpus :: UnitTestOptions -> Corpus -> Text -> [AbiType] -> Gen AbiValue
genWithCorpus _ corpus sig tps = do
b <- arbitrary :: Gen Bool
if b then genAbiValue (AbiTupleType $ Vector.fromList tps)
else do
-- TODO: also check that the contract matches here
let examples = [cd | (_, sig', cd) <- (fmap snd) . Map.toList $ corpus, sig' == sig]
case examples of
[] -> genAbiValue (AbiTupleType $ Vector.fromList tps)
_ -> Test.QuickCheck.elements examples >>= mutateAbiValue
genWithCorpus UnitTestOptions { .. } corpus sig tps = do
-- TODO: also check that the contract matches here
let examples = [cd | (_, sig', cd) <- (fmap snd) . Map.toList $ corpus, sig' == sig]
if (null examples)
then genAbiValue (AbiTupleType $ Vector.fromList tps)
else frequency [(mutations, Test.QuickCheck.elements examples >>= mutateAbiValue), (100 - mutations, genAbiValue (AbiTupleType $ Vector.fromList tps))]

-- | Randomly generates the calldata arguments and runs the test, updates the corpus with a new example if we explored a new path
fuzzTest :: UnitTestOptions -> Text -> [AbiType] -> VM -> StateT Corpus IO FuzzResult
Expand Down Expand Up @@ -482,8 +481,6 @@ runOne opts@UnitTestOptions{..} vm testName args = do
, vm''
)

data FuzzResult = Pass | Fail VM String

-- | Define the thread spawner for property based tests
fuzzRun :: UnitTestOptions -> VM -> Text -> [AbiType] -> IO (Text, Either Text Text, VM)
fuzzRun opts@UnitTestOptions{..} vm testName types = do
Expand Down