@@ -21,6 +21,7 @@ import Echidna.Types.Config
2121import Echidna.Types.Campaign (CampaignConf (.. ))
2222import Echidna.Test (getResultFromVM , checkETest )
2323
24+ -- | Top level function to shrink the complexity of the sequence of transactions once
2425shrinkTest
2526 :: (MonadIO m , MonadThrow m , MonadRandom m , MonadReader Env m )
2627 => VM Concrete RealWorld
@@ -29,22 +30,28 @@ shrinkTest
2930shrinkTest vm test = do
3031 env <- ask
3132 case test. state of
33+ -- If we run out of tries to shrink, return the sequence as we have them
3234 Large i | i >= env. cfg. campaignConf. shrinkLimit && not (isOptimizationTest test) ->
3335 pure $ Just test { state = Solved }
3436 Large i ->
37+ -- Start removing the reverts, if any
3538 do repro <- removeReverts vm test. reproducer
36- let rr = removeUselessNoCalls $ catNoCalls repro
39+ let rr = removeUselessNoCalls $ catNoCalls repro
40+ -- Check if the sequence can be reduced, in practice this is almost never fails
41+ -- since the canShrinkTx function is hard to enforce for all transaction in the sequence
3742 if length rr > 1 || any canShrinkTx rr then do
3843 maybeShrunk <- shrinkSeq vm (checkETest test) test. value rr
44+ -- check if the shrinked sequence passes the test or not
3945 pure $ case maybeShrunk of
46+ -- the test still fails, let's create another test with the reduced sequence
4047 Just (txs, val, vm') -> do
4148 Just test { state = Large (i + 1 )
4249 , reproducer = txs
4350 , vm = Just vm'
4451 , result = getResultFromVM vm'
4552 , value = val }
4653 Nothing ->
47- -- No success with shrinking this time, just bump trials
54+ -- The test passed, so no success with shrinking this time, just bump number of tries to shrink
4855 Just test { state = Large (i + 1 ), reproducer = rr}
4956 else
5057 pure $ Just test { state = if isOptimizationTest test
@@ -55,10 +62,15 @@ shrinkTest vm test = do
5562replaceByNoCall :: Tx -> Tx
5663replaceByNoCall tx = tx { call = NoCall }
5764
65+ -- | Given a sequence of transactions, remove useless NoCalls. These
66+ -- are when the NoCall have both zero increment in timestamp and block number.
5867removeUselessNoCalls :: [Tx ] -> [Tx ]
5968removeUselessNoCalls = mapMaybe f
6069 where f tx = if isUselessNoCall tx then Nothing else Just tx
6170
71+ -- | Given a VM and a sequence of transactions, execute each transaction except the last one.
72+ -- If a transaction reverts, replace it by a "NoCall" with the same parameters as the original call
73+ -- (e.g. same block increment timestamp and number)
6274removeReverts :: (MonadIO m , MonadReader Env m , MonadThrow m ) => VM Concrete RealWorld -> [Tx ] -> m [Tx ]
6375removeReverts vm txs = do
6476 let (itxs, le) = (init txs, last txs)
@@ -83,10 +95,14 @@ shrinkSeq
8395 -> [Tx ]
8496 -> m (Maybe ([Tx ], TestValue , VM Concrete RealWorld ))
8597shrinkSeq vm f v txs = do
98+ -- apply one of the two possible simplification strategies (shrunk or shorten) with equal probability
8699 txs' <- uniform =<< sequence [shorten, shrunk]
100+ -- remove certain type of "no calls"
87101 let txs'' = removeUselessNoCalls txs'
102+ -- check if the sequence still triggers a failed transaction
88103 (value, vm') <- check txs'' vm
89- -- if the test passed it means we didn't shrink successfully
104+ -- if the test passed it means we didn't shrink successfully (returns Nothing)
105+ -- otherwise, return a reduced sequence of transaction
90106 pure $ case (value,v) of
91107 (BoolValue False , _) -> Just (txs'', value, vm')
92108 (IntValue x, IntValue y) | x >= y -> Just (txs'', value, vm')
@@ -96,9 +112,15 @@ shrinkSeq vm f v txs = do
96112 check (x: xs') vm' = do
97113 (_, vm'') <- execTx vm' x
98114 check xs' vm''
115+ -- | Simplify a sequence of transactions reducing the complexity of its arguments (using shrinkTx)
116+ -- and then reducing its sender (using shrinkSender)
99117 shrunk = mapM (shrinkSender <=< shrinkTx) txs
118+ -- | Simplifiy a sequence of transactions randomly dropping one transaction (with uniform selection)
100119 shorten = (\ i -> take i txs ++ drop (i + 1 ) txs) <$> getRandomR (0 , length txs)
101120
121+ -- | Given a transaction, replace the sender of the transaction by another one
122+ -- which is simpler (e.g. it is closer to zero). Usually this means that
123+ -- simplified transactions will try to use 0x10000 as the same caller
102124shrinkSender :: (MonadReader Env m , MonadRandom m ) => Tx -> m Tx
103125shrinkSender x = do
104126 senderSet <- asks (. cfg. solConf. sender)
0 commit comments