@@ -15,6 +15,7 @@ import System.Process (readProcessWithExitCode)
1515import Text.Read (readMaybe )
1616
1717import Common (solved , passed , testContract , testContractNamed )
18+ import Echidna.Config (defaultPsender )
1819import Echidna.Types.Config (Env )
1920import Echidna.Types.Campaign (WorkerState )
2021import EVM.ABI (AbiValue (.. ))
@@ -29,6 +30,7 @@ foundryTestGenTests = testGroup "Foundry test generation"
2930 , testCase " correctly encodes bytes1" testBytes1Encoding
3031 , testCase " fallback function syntax" testFallbackSyntax
3132 , testCase " null bytes in arguments" testNullBytes
33+ , testCase " property test generates assertFalse" testPropertyTestGen
3234 , testGroup " Concrete execution (fuzzing)"
3335 [ testForgeStd " solves assertTrue"
3436 " foundry/FoundryAsserts.sol"
@@ -159,6 +161,9 @@ foundryTestGenTests = testGroup "Foundry test generation"
159161 FuzzWorker
160162 [ (" vm.assume should not be treated as test failure" , passed " test_assume_filters" )
161163 ]
164+ , testContract " foundry/PropertyRepro.sol" (Just " foundry/PropertyRepro.yaml" )
165+ [ (" property test should be detected" , solved " echidna_counter_is_zero" )
166+ ]
162167 ]
163168 , testGroup " Symbolic execution (SMT solving)"
164169 [ testForgeStd " solves assertTrue"
@@ -287,7 +292,7 @@ testForgeCompiles tmpDirSuffix contractName testData outputFile = do
287292 copyFile contractPath (tmpDir ++ " /src/" ++ contractFile)
288293
289294 -- Generate test and add contract import after forge-std import
290- let generated = TL. unpack $ foundryTest (Just (pack contractName)) testData
295+ let generated = TL. unpack $ foundryTest (Just (pack contractName)) defaultPsender testData
291296 forgeStdImport = pack " import \" forge-std/Test.sol\" ;"
292297 contractImport = pack $ " import \" ../src/" ++ contractFile ++ " \" ;"
293298 testWithImport = unpack $ replace forgeStdImport
@@ -318,11 +323,38 @@ testBytes1Encoding = do
318323 , delay = (0 , 0 )
319324 }
320325 test = mkMinimalTest { reproducer = [reproducerTx] }
321- generated = TL. unpack $ foundryTest (Just " FoundryTestTarget" ) test
326+ generated = TL. unpack $ foundryTest (Just " FoundryTestTarget" ) defaultPsender test
322327 if " hex\" 92\" " `isInfixOf` generated
323328 then pure ()
324329 else assertFailure $ " bytes1 not correctly encoded: " ++ generated
325330
331+ -- | Test that property mode tests generate assertFalse with psender prank.
332+ testPropertyTestGen :: IO ()
333+ testPropertyTestGen = do
334+ let
335+ reproducerTx = Tx
336+ { call = SolCall (" inc" , [] )
337+ , src = 0x10000
338+ , dst = 0
339+ , value = 0
340+ , gas = 0
341+ , gasprice = 0
342+ , delay = (0 , 0 )
343+ }
344+ test = mkMinimalTest
345+ { testType = PropertyTest " echidna_counter_is_zero" 0
346+ , reproducer = [reproducerTx]
347+ }
348+ generated = TL. unpack $ foundryTest (Just " PropertyRepro" ) defaultPsender test
349+ assertBool (" should contain assertFalse call, got: " ++ generated)
350+ (" assertFalse(Target.echidna_counter_is_zero())" `isInfixOf` generated)
351+ assertBool (" should contain vm.prank for psender, got: " ++ generated)
352+ (" vm.prank(" `isInfixOf` generated)
353+ assertBool (" should contain vm.stopPrank, got: " ++ generated)
354+ (" vm.stopPrank()" `isInfixOf` generated)
355+ assertBool (" should contain inc() call, got: " ++ generated)
356+ (" Target.inc()" `isInfixOf` generated)
357+
326358-- | Wrapper for testContractNamed that skips if solc < 0.8.13.
327359testForgeStd :: String -> FilePath -> Maybe String -> Maybe FilePath -> WorkerType -> [(String , (Env , WorkerState ) -> IO Bool )] -> TestTree
328360testForgeStd name fp contract config workerType checks =
0 commit comments