55module Echidna.Output.Foundry (foundryTest ) where
66
77import Data.Aeson (Value (.. ), object , (.=) )
8- import Data.Functor ((<&>) )
9- import Data.List (elemIndex , nub )
8+ import Data.List (elemIndex , isPrefixOf , nub )
109import Data.Maybe (fromMaybe , mapMaybe )
1110import Data.Text (Text , unpack )
1211import Data.Text.Lazy (fromStrict )
@@ -33,33 +32,67 @@ foundryTest :: Maybe Text -> Addr -> EchidnaTest -> TL.Text
3332foundryTest mContractName psender test =
3433 case test. testType of
3534 AssertionTest {} ->
36- let testData = createTestData mContractName Nothing test
35+ let testData = createTestData mContractName Nothing Nothing test
3736 in fromStrict $ substituteValue template (toMustache testData)
3837 PropertyTest name _ ->
39- let testData = createTestData mContractName (Just (name, psender)) test
38+ let testData = createTestData mContractName (Just (name, psender)) Nothing test
39+ in fromStrict $ substituteValue template (toMustache testData)
40+ CallTest name _ | " AssertionFailed" `isPrefixOf` unpack name ->
41+ -- Echidna detects assertion failures via events named AssertionFailed
42+ -- with any argument types (see checkAssertionEvent in Echidna.Test).
43+ -- We check all overloads defined in crytic's fuzzlib (LibLog.sol):
44+ -- AssertionFailed()
45+ -- AssertionFailed(string)
46+ -- AssertionFailed(string,string)
47+ -- AssertionFailed(string,bytes)
48+ -- AssertionFailed(string,uint256)
49+ -- AssertionFailed(string,int256)
50+ -- AssertionFailed(string,address)
51+ -- AssertionFailed(string,bool)
52+ -- AssertionFailed(string,bytes32)
53+ let eventAssert = Just $
54+ " // Check that an AssertionFailed event was emitted\n "
55+ ++ " Vm.Log[] memory entries = vm.getRecordedLogs();\n "
56+ ++ " bool found = false;\n "
57+ ++ " for (uint i = 0; i < entries.length; i++) {\n "
58+ ++ " if (entries[i].topics.length > 0 && _isAssertionFailed(entries[i].topics[0])) {\n "
59+ ++ " found = true;\n "
60+ ++ " break;\n "
61+ ++ " }\n "
62+ ++ " }\n "
63+ ++ " assertTrue(found, \" Expected AssertionFailed event\" );"
64+ testData = createTestData mContractName Nothing eventAssert test
4065 in fromStrict $ substituteValue template (toMustache testData)
4166 _ -> " "
4267
4368-- | Create an Aeson Value from test data for the Mustache template.
4469-- When a property name and psender are provided, a final assertion is added
4570-- to call the property from psender and check it returns false.
46- createTestData :: Maybe Text -> Maybe (Text , Addr ) -> EchidnaTest -> Value
47- createTestData mContractName mProperty test =
71+ -- When an event assertion is provided, vm.recordLogs() is added at the start
72+ -- and the event check is added at the end.
73+ createTestData :: Maybe Text -> Maybe (Text , Addr ) -> Maybe String -> EchidnaTest -> Value
74+ createTestData mContractName mProperty mEventAssert test =
4875 let
4976 senders = nub $ map (. src) test. reproducer
5077 actors = zipWith actorObject senders [1 .. ]
5178 repro = mapMaybe (foundryTx senders) test. reproducer
5279 cName = fromMaybe " YourContract" mContractName
53- propAssertion = mProperty <&> \ (name, addr) ->
54- " vm.stopPrank();\n vm.prank(" ++ formatAddr addr ++ " );\n "
55- ++ " assertFalse(Target." ++ unpack name ++ " ());"
80+ propAssertion = case mProperty of
81+ Just (name, addr) -> Just $
82+ " vm.stopPrank();\n vm.prank(" ++ formatAddr addr ++ " );\n "
83+ ++ " assertFalse(Target." ++ unpack name ++ " ());"
84+ Nothing -> mEventAssert
85+ preamble = case mEventAssert of
86+ Just _ -> Just (" vm.recordLogs();" :: String )
87+ Nothing -> Nothing
5688 in
5789 object
5890 [ " testName" .= (" FoundryTest" :: Text )
5991 , " contractName" .= cName
6092 , " actors" .= actors
6193 , " reproducer" .= repro
6294 , " propertyAssertion" .= propAssertion
95+ , " preamble" .= preamble
6396 ]
6497
6598-- | Create a JSON object for an actor.
0 commit comments