Skip to content

Commit 6e589d4

Browse files
Detect when a property/optimization is invalid (#1536)
1 parent 8e8bb5d commit 6e589d4

File tree

9 files changed

+57
-15
lines changed

9 files changed

+57
-15
lines changed

lib/Echidna/Solidity.hs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import Echidna.ABI
4242
import Echidna.Deploy (deployContracts, deployBytecodes)
4343
import Echidna.Exec (execTx, execTxWithCov, initialVM)
4444
import Echidna.SourceAnalysis.Slither
45-
import Echidna.Test (createTests, isAssertionMode, isPropertyMode, isFoundryMode)
45+
import Echidna.Test (createTests, isAssertionMode, isPropertyMode, isFoundryMode, isOptimizationMode)
4646
import Echidna.Types.Campaign (CampaignConf(..))
4747
import Echidna.Types.Config (EConfig(..), Env(..))
4848
import Echidna.Types.Signature
@@ -322,6 +322,18 @@ mkTests solConf campaignConf mainContract = do
322322
Just (t, _) -> throwM $ TestArgsFound t
323323
Nothing -> pure ()
324324

325+
-- Check that property functions return bool
326+
when (isPropertyMode solConf.testMode) $
327+
forM_ (Map.elems mainContract.abiMap) $ \method ->
328+
when (solConf.prefix `T.isPrefixOf` method.name && map snd method.output /= [AbiBoolType]) $
329+
throwM $ PropertyWithoutReturn method.name
330+
331+
-- Check that optimization functions return int256
332+
when (isOptimizationMode solConf.testMode) $
333+
forM_ (Map.elems mainContract.abiMap) $ \method ->
334+
when (solConf.prefix `T.isPrefixOf` method.name && map snd method.output /= [AbiIntType 256]) $
335+
throwM $ OptimizationWithWrongReturn method.name
336+
325337
pure $ createTests solConf.testMode
326338
solConf.testDestruction
327339
testNames

lib/Echidna/Test.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ isFoundryMode :: TestMode -> Bool
8080
isFoundryMode "foundry" = True
8181
isFoundryMode _ = False
8282

83+
isOptimizationMode :: TestMode -> Bool
84+
isOptimizationMode "optimization" = True
85+
isOptimizationMode _ = False
86+
8387
createTests
8488
:: TestMode
8589
-> Bool

lib/Echidna/Types/Solidity.hs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ data SolException
2323
| SolcReadFailure
2424
| NoContracts
2525
| TestArgsFound Text
26+
| PropertyWithoutReturn Text
27+
| OptimizationWithWrongReturn Text
2628
| ContractNotFound Text
2729
| NoBytecode Text
2830
| NoFuncs
2931
| NoTests
30-
| OnlyTests
32+
3133
| ConstructorArgs String
3234
| DeploymentFailed Addr Text
3335
| SetUpCallFailed Text
@@ -43,10 +45,12 @@ instance Show SolException where
4345
NoContracts -> "No contracts found in given file"
4446
ContractNotFound c -> "Given contract " ++ show c ++ " not found in given file"
4547
TestArgsFound t -> "Test " ++ show t ++ " has arguments, aborting"
48+
PropertyWithoutReturn t -> "Property " ++ show t ++ " does not return bool. Property functions must have signature: function " ++ unpack t ++ "() public returns (bool)"
49+
OptimizationWithWrongReturn t -> "Optimization " ++ show t ++ " does not return int256. Optimization functions must have signature: function " ++ unpack t ++ "() public returns (int256)"
4650
NoBytecode t -> "No bytecode found for contract " ++ show t
4751
NoFuncs -> "ABI is empty, are you sure your constructor is right?"
4852
NoTests -> "No tests found in ABI. If you are using assert(), use --test-mode assertion"
49-
OnlyTests -> "Only tests and no public functions found in ABI"
53+
5054
ConstructorArgs s -> "Constructor arguments are required: " ++ s
5155
NoCryticCompile -> "crytic-compile not installed or not found in PATH. To install it, run:\n pip install crytic-compile"
5256
InvalidMethodFilters f -> "Applying the filter " ++ show f ++ " to the methods produces an empty list. Are you filtering the correct functions using `filterFunctions` or fuzzing the correct contract?"

src/test/Common.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module Common
44
, testContract
55
, testContractV
66
, solcV
7+
, withSolcVersion
78
, testContract'
89
, testContractNamed
910
, checkConstructorConditions

src/test/Tests/Compile.hs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,26 @@ module Tests.Compile (compilationTests) where
22

33
import Control.Monad (void)
44
import Control.Monad.Catch (catch)
5+
import Data.SemVer qualified
56
import Data.Text (Text)
67
import Test.Tasty (TestTree, testGroup)
78
import Test.Tasty.HUnit (testCase, assertBool)
89

9-
import Common (testConfig, loadSolTests)
10+
import Common (testConfig, solcV, withSolcVersion, loadSolTests)
1011
import Echidna.Solidity (compileContracts)
1112
import Echidna.Types.Config (EConfig(..))
12-
import Echidna.Types.Solidity (SolException(..))
13+
import Echidna.Types.Solidity (SolConf(..), SolException(..))
1314

1415
compilationTests :: TestTree
1516
compilationTests = testGroup "Compilation and loading tests"
1617
[ loadFails "bad/nocontract.sol" (Just "c") "failed to warn on contract not found" $
1718
\case ContractNotFound _ -> True; _ -> False
18-
, loadFails "bad/nobytecode.sol" Nothing "failed to warn on abstract contract" $
19+
, loadFailsV (>= solcV (0,6,0)) "bad/nobytecode.sol" Nothing "failed to warn on abstract contract" $
1920
\case NoBytecode _ -> True; _ -> False
2021
, loadFails "bad/nofuncs.sol" Nothing "failed to warn on no functions found" $
2122
\case NoFuncs -> True; _ -> False
2223
, loadFails "bad/notests.sol" Nothing "failed to warn on no tests found" $
2324
\case NoTests -> True; _ -> False
24-
, loadFails "bad/onlytests.sol" Nothing "failed to warn on no non-tests found" $
25-
\case OnlyTests -> True; _ -> False
2625
, loadFails "bad/testargs.sol" Nothing "failed to warn on test args found" $
2726
\case TestArgsFound _ -> True; _ -> False
2827
, loadFails "bad/consargs.sol" Nothing "failed to warn on cons args found" $
@@ -33,11 +32,26 @@ compilationTests = testGroup "Compilation and loading tests"
3332
\case DeploymentFailed _ _ -> True; _ -> False
3433
, loadFails "basic/eip-170.sol" Nothing "failed to warn on a failed deployment" $
3534
\case DeploymentFailed _ _ -> True; _ -> False
35+
, loadFailsWith "bad/propaliased.sol" Nothing "property" "failed to warn on property without bool return" $
36+
\case PropertyWithoutReturn _ -> True; _ -> False
37+
, loadFailsWith "bad/optaliased.sol" Nothing "optimization" "failed to warn on optimization with wrong return" $
38+
\case OptimizationWithWrongReturn _ -> True; _ -> False
3639
]
3740

3841
loadFails :: FilePath -> Maybe Text -> String -> (SolException -> Bool) -> TestTree
39-
loadFails fp c e p = testCase fp . catch tryLoad $ assertBool e . p where
42+
loadFails fp c = loadFailsWith fp c "property"
43+
44+
loadFailsV :: (Data.SemVer.Version -> Bool) -> FilePath -> Maybe Text -> String -> (SolException -> Bool) -> TestTree
45+
loadFailsV v fp c e p = testCase fp $ withSolcVersion (Just v) $
46+
catch tryLoad (assertBool e . p) where
4047
tryLoad = do
4148
let cfg = testConfig
4249
buildOutput <- compileContracts cfg.solConf (pure fp)
4350
void $ loadSolTests cfg buildOutput c
51+
52+
loadFailsWith :: FilePath -> Maybe Text -> String -> String -> (SolException -> Bool) -> TestTree
53+
loadFailsWith fp c mode e p = testCase fp . catch tryLoad $ assertBool e . p where
54+
tryLoad = do
55+
let cfg = testConfig { solConf = testConfig.solConf { testMode = mode } }
56+
buildOutput <- compileContracts cfg.solConf (pure fp)
57+
void $ loadSolTests cfg buildOutput c

tests/solidity/bad/nobytecode.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
contract Abstract {
2-
function nothere() pure public {}
3-
function echidna_nothere() pure public {}
1+
abstract contract Abstract {
2+
function nothere() public virtual;
3+
function echidna_nothere() public virtual returns (bool);
44
}

tests/solidity/bad/onlytests.sol

Lines changed: 0 additions & 3 deletions
This file was deleted.

tests/solidity/bad/optaliased.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
contract Test {
2+
function foo() public view {}
3+
function echidna_opt_test() public view returns (bool) {
4+
return true;
5+
}
6+
}

tests/solidity/bad/propaliased.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
contract Test {
2+
function foo() public view {}
3+
function echidna_test() public view {}
4+
}

0 commit comments

Comments
 (0)