@@ -6,7 +6,7 @@ module Echidna.Output.Foundry (foundryTest) where
66
77import Data.Aeson (Value (.. ), object , (.=) )
88import Data.Functor ((<&>) )
9- import Data.List (elemIndex , nub )
9+ import Data.List (elemIndex , isPrefixOf , nub )
1010import Data.Maybe (fromMaybe , mapMaybe )
1111import Data.Text (Text , unpack )
1212import Data.Text.Lazy (fromStrict )
@@ -33,33 +33,67 @@ foundryTest :: Maybe Text -> Addr -> EchidnaTest -> TL.Text
3333foundryTest mContractName psender test =
3434 case test. testType of
3535 AssertionTest {} ->
36- let testData = createTestData mContractName Nothing test
36+ let testData = createTestData mContractName Nothing Nothing test
3737 in fromStrict $ substituteValue template (toMustache testData)
3838 PropertyTest name _ ->
39- let testData = createTestData mContractName (Just (name, psender)) test
39+ let testData = createTestData mContractName (Just (name, psender)) Nothing test
40+ in fromStrict $ substituteValue template (toMustache testData)
41+ CallTest name _ | " AssertionFailed" `isPrefixOf` unpack name ->
42+ -- Echidna detects assertion failures via events named AssertionFailed
43+ -- with any argument types (see checkAssertionEvent in Echidna.Test).
44+ -- We check all overloads defined in crytic's fuzzlib (LibLog.sol):
45+ -- AssertionFailed()
46+ -- AssertionFailed(string)
47+ -- AssertionFailed(string,string)
48+ -- AssertionFailed(string,bytes)
49+ -- AssertionFailed(string,uint256)
50+ -- AssertionFailed(string,int256)
51+ -- AssertionFailed(string,address)
52+ -- AssertionFailed(string,bool)
53+ -- AssertionFailed(string,bytes32)
54+ let eventAssert = Just $
55+ " // Check that an AssertionFailed event was emitted\n "
56+ ++ " Vm.Log[] memory entries = vm.getRecordedLogs();\n "
57+ ++ " bool found = false;\n "
58+ ++ " for (uint i = 0; i < entries.length; i++) {\n "
59+ ++ " if (entries[i].topics.length > 0 && _isAssertionFailed(entries[i].topics[0])) {\n "
60+ ++ " found = true;\n "
61+ ++ " break;\n "
62+ ++ " }\n "
63+ ++ " }\n "
64+ ++ " assertTrue(found, \" Expected AssertionFailed event\" );"
65+ testData = createTestData mContractName Nothing eventAssert test
4066 in fromStrict $ substituteValue template (toMustache testData)
4167 _ -> " "
4268
4369-- | Create an Aeson Value from test data for the Mustache template.
4470-- When a property name and psender are provided, a final assertion is added
4571-- 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 =
72+ -- When an event assertion is provided, vm.recordLogs() is added at the start
73+ -- and the event check is added at the end.
74+ createTestData :: Maybe Text -> Maybe (Text , Addr ) -> Maybe String -> EchidnaTest -> Value
75+ createTestData mContractName mProperty mEventAssert test =
4876 let
4977 senders = nub $ map (. src) test. reproducer
5078 actors = zipWith actorObject senders [1 .. ]
5179 repro = mapMaybe (foundryTx senders) test. reproducer
5280 cName = fromMaybe " YourContract" mContractName
53- propAssertion = mProperty <&> \ (name, addr) ->
54- " vm.stopPrank();\n vm.prank(" ++ formatAddr addr ++ " );\n "
55- ++ " assertFalse(Target." ++ unpack name ++ " ());"
81+ propAssertion = case mProperty of
82+ Just (name, addr) -> Just $
83+ " vm.stopPrank();\n vm.prank(" ++ formatAddr addr ++ " );\n "
84+ ++ " assertFalse(Target." ++ unpack name ++ " ());"
85+ Nothing -> mEventAssert
86+ preamble = case mEventAssert of
87+ Just _ -> Just (" vm.recordLogs();" :: String )
88+ Nothing -> Nothing
5689 in
5790 object
5891 [ " testName" .= (" FoundryTest" :: Text )
5992 , " contractName" .= cName
6093 , " actors" .= actors
6194 , " reproducer" .= repro
6295 , " propertyAssertion" .= propAssertion
96+ , " preamble" .= preamble
6397 ]
6498
6599-- | Create a JSON object for an actor.
0 commit comments