@@ -5,7 +5,7 @@ module Echidna.Campaign where
55
66import Control.Concurrent
77import Control.DeepSeq (force )
8- import Control.Monad (replicateM , replicateM_ , when , unless , void , forM_ )
8+ import Control.Monad (replicateM , when , unless , void , forM_ )
99import Control.Monad.Catch (MonadThrow (.. ))
1010import Control.Monad.Random.Strict (MonadRandom , RandT , evalRandT )
1111import Control.Monad.Reader (MonadReader , asks , liftIO , ask )
@@ -42,7 +42,7 @@ import Echidna.Shrink (shrinkTest)
4242import Echidna.Solidity (chooseContract )
4343import Echidna.SymExec.Common (extractTxs , extractErrors )
4444import Echidna.SymExec.Symbolic (forceAddr )
45- import Echidna.SymExec.Exploration (exploreContract )
45+ import Echidna.SymExec.Exploration (exploreContract , getTargetMethodFromTx , getRandomTargetMethod )
4646import Echidna.SymExec.Verification (verifyMethod )
4747import Echidna.Test
4848import Echidna.Transaction
@@ -51,7 +51,7 @@ import Echidna.Types.Campaign
5151import Echidna.Types.Corpus (Corpus , corpusSize )
5252import Echidna.Types.Coverage (coverageStats )
5353import Echidna.Types.Config
54- import Echidna.Types.Random (rElem , shuffleIO )
54+ import Echidna.Types.Random (rElem )
5555import Echidna.Types.Signature (FunctionName )
5656import Echidna.Types.Test
5757import Echidna.Types.Test qualified as Test
@@ -116,8 +116,7 @@ runSymWorker
116116 -- ^ Initial corpus of transactions
117117 -> Maybe Text -- ^ Specified contract name
118118 -> m (WorkerStopReason , WorkerState )
119- runSymWorker callback vm dict workerId initialCorpus name = do
120- shuffleCorpus <- liftIO $ shuffleIO initialCorpus
119+ runSymWorker callback vm dict workerId _ name = do
121120 cfg <- asks (. cfg)
122121 let nworkers = getNFuzzWorkers cfg. campaignConf -- getNFuzzWorkers, NOT getNWorkers
123122 eventQueue <- asks (. eventQueue)
@@ -134,13 +133,6 @@ runSymWorker callback vm dict workerId initialCorpus name = do
134133 flip evalRandT (mkStdGen effectiveSeed) $ do -- unused but needed for callseq
135134 lift callback
136135 listenerLoop listenerFunc chan nworkers
137- void $ replayCorpus vm initialCorpus
138- if null shuffleCorpus then
139- replicateM_ 10 $ symexecTxs True [] -- TODO: determine how many times to symexec here
140- else
141- mapM_ (symexecTxs False . snd ) shuffleCorpus
142- liftIO $ putStrLn " Symbolic exploration started purely random!"
143- replicateM_ 100 $ mapM_ (symexecTxs True . snd ) shuffleCorpus
144136 pure SymbolicExplorationDone
145137
146138 where
@@ -164,8 +156,53 @@ runSymWorker callback vm dict workerId initialCorpus name = do
164156 listenerFunc (_, WorkerEvent _ _ (NewCoverage {transactions})) = do
165157 void $ callseq vm transactions
166158 symexecTxs False transactions
159+ shrinkAndRandomlyExplore transactions (10 :: Int )
167160 listenerFunc _ = pure ()
168161
162+ shrinkAndRandomlyExplore _ 0 = do
163+ testRefs <- asks (. testRefs)
164+ tests <- liftIO $ traverse readIORef testRefs
165+ CampaignConf {shrinkLimit} <- asks (. cfg. campaignConf)
166+ if any shrinkable tests then shrinkLoop shrinkLimit else return ()
167+
168+ shrinkAndRandomlyExplore txs n = do
169+ testRefs <- asks (. testRefs)
170+ tests <- liftIO $ traverse readIORef testRefs
171+ CampaignConf {stopOnFail, shrinkLimit} <- asks (. cfg. campaignConf)
172+ if stopOnFail && any final tests then
173+ lift callback -- >> pure FastFailed
174+ else if any shrinkable tests then do
175+ shrinkLoop shrinkLimit
176+ shrinkAndRandomlyExplore txs n
177+ else do
178+ symexecTxs False txs
179+ shrinkAndRandomlyExplore txs (n - 1 )
180+
181+
182+ shrinkable test =
183+ case test. state of
184+ -- we shrink only tests which were solved on this
185+ -- worker, see 'updateOpenTest'
186+ Large _ | test. workerId == Just workerId -> True
187+ _ -> False
188+
189+ final test =
190+ case test. state of
191+ Solved -> True
192+ Failed _ -> True
193+ _ -> False
194+
195+
196+ shrinkLoop 0 = return ()
197+ shrinkLoop n = do
198+ lift callback
199+ updateTests $ \ test -> do
200+ if test. workerId == Just workerId then
201+ shrinkTest vm test
202+ else
203+ pure Nothing
204+ shrinkLoop (n - 1 )
205+
169206 symexecTxs onlyRandom txs = mapM_ symexecTx =<< txsToTxAndVmsSym onlyRandom txs
170207
171208 -- | Turn a list of transactions into inputs for symexecTx:
@@ -198,8 +235,21 @@ runSymWorker callback vm dict workerId initialCorpus name = do
198235 dapp <- asks (. dapp)
199236 let cs = Map. elems dapp. solcByName
200237 contract <- chooseContract cs name
201- (threadId, symTxsChan) <- exploreContract contract tx vm'
202-
238+ failedTests <- findFailedTests
239+ let failedTestSignatures = map getAssertionSignature failedTests
240+ case tx of
241+ Nothing -> getRandomTargetMethod contract failedTestSignatures >>= \ case
242+ Nothing -> do
243+ error " No suitable method found for symbolic execution"
244+ Just method -> exploreAndVerify contract method vm' txsBase
245+ Just t -> getTargetMethodFromTx t contract failedTestSignatures >>= \ case
246+ Nothing -> do
247+ return ()
248+ Just method -> do
249+ exploreAndVerify contract method vm' txsBase
250+
251+ exploreAndVerify contract method vm' txsBase = do
252+ (threadId, symTxsChan) <- exploreContract contract method vm'
203253 modify' (\ ws -> ws { runningThreads = [threadId] })
204254 lift callback
205255
@@ -217,10 +267,8 @@ runSymWorker callback vm dict workerId initialCorpus name = do
217267 -- We can't do callseq vm' [symTx] because callseq might post the full call sequence as an event
218268 newCoverage <- or <$> mapM (\ symTx -> snd <$> callseq vm (txsBase <> [symTx])) txs
219269
220- when (not newCoverage && null errors && not (null txs)) ( do
221- liftIO $ mapM_ print txsBase
222- liftIO $ putStrLn $ " Last txs: " <> show txs
223- error " No errors but symbolic execution found valid txs breaking assertions. Something is wrong." )
270+ when (not newCoverage && null errors && not (null txs)) (
271+ pushWorkerEvent $ SymExecError " No errors but symbolic execution found valid txs breaking assertions. Something is wrong." )
224272 unless newCoverage (pushWorkerEvent SymNoNewCoverage )
225273
226274 verifyMethods = do
@@ -574,6 +622,14 @@ updateTests f = do
574622 Just test' -> liftIO $ writeIORef testRef test'
575623 Nothing -> pure ()
576624
625+ findFailedTests
626+ :: (MonadIO m , MonadReader Env m , MonadState WorkerState m )
627+ => m [EchidnaTest ]
628+ findFailedTests = do
629+ testRefs <- asks (. testRefs)
630+ tests <- liftIO $ traverse readIORef testRefs
631+ pure $ filter didFail tests
632+
577633-- | Update an open test after checking if it is falsified by the 'reproducer'
578634updateOpenTest
579635 :: (MonadIO m , MonadThrow m , MonadRandom m , MonadReader Env m , MonadState WorkerState m )
0 commit comments