diff --git a/docs/src/project_configuration/testing_config.md b/docs/src/project_configuration/testing_config.md index 12d01b52..aa01d47b 100644 --- a/docs/src/project_configuration/testing_config.md +++ b/docs/src/project_configuration/testing_config.md @@ -177,6 +177,15 @@ contract MyContract { > **Note**: If you are moving over from Echidna, you can add `echidna_` as a test prefix to quickly port over the property tests from it. - **Default**: `[property_]` + +### `returnBool` + +- **Type**: [Bool] +- **Description**: If the property returns a boolean or not + > **Note**: If you are using invariants developed for Foundry's invariant testing, set this to false. +- **Default**: `true` + + ## Optimization Testing Configuration ### `enabled` diff --git a/docs/src/static/function_level_testing_medusa.json b/docs/src/static/function_level_testing_medusa.json index 5e284eef..398b2469 100644 --- a/docs/src/static/function_level_testing_medusa.json +++ b/docs/src/static/function_level_testing_medusa.json @@ -40,7 +40,8 @@ }, "propertyTesting": { "enabled": true, - "testPrefixes": ["property_"] + "testPrefixes": ["property_"], + "returnBool": true }, "optimizationTesting": { "enabled": true, diff --git a/docs/src/static/medusa.json b/docs/src/static/medusa.json index 4326aa56..4ee6048f 100644 --- a/docs/src/static/medusa.json +++ b/docs/src/static/medusa.json @@ -44,7 +44,8 @@ }, "propertyTesting": { "enabled": true, - "testPrefixes": ["property_"] + "testPrefixes": ["property_"], + "returnBool": true }, "optimizationTesting": { "enabled": true, diff --git a/docs/src/testing/writing-tests.md b/docs/src/testing/writing-tests.md index f8aacca2..aee5cd22 100644 --- a/docs/src/testing/writing-tests.md +++ b/docs/src/testing/writing-tests.md @@ -12,6 +12,7 @@ For more advanced information and documentation on how the various modes work an Property tests are represented as functions within a Solidity contract whose names are prefixed with a prefix specified by the `testPrefixes` configuration option (`fuzz_` is the default test prefix). Additionally, they must take no arguments and return a `bool` indicating if the test succeeded. + ```solidity contract TestXY { uint x; @@ -34,6 +35,8 @@ contract TestXY { `medusa` deploys your contract containing property tests and generates a sequence of calls to execute against all publicly accessible methods. After each function call, it calls upon your property tests to ensure they return a `true` (success) status. +If you are using [Foundry's invariant testing](https://getfoundry.sh/forge/advanced-testing/invariant-testing/#conditional-invariants), you can define the properties to not return a boolean by setting `returnBool` to false. + ### Testing in property-mode To begin a fuzzing campaign in property-mode, you can run `medusa fuzz` or `medusa fuzz --config [config_path]`. diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index c1d42bf6..1015adea 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -310,6 +310,9 @@ type PropertyTestingConfig struct { // TestPrefixes dictates what method name prefixes will determine if a contract method is a property test. TestPrefixes []string `json:"testPrefixes"` + + // TestReturnBool dictactes if the properties should return a bool or not + TestReturnBool bool `json:"returnBool"` } // OptimizationTestingConfig describes the configuration options used for optimization testing diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 2c11e38e..455ac3f5 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -83,6 +83,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { TestPrefixes: []string{ "property_", }, + TestReturnBool: true, }, OptimizationTesting: OptimizationTestingConfig{ Enabled: true, diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index d04d9ef0..048840ce 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -411,7 +411,8 @@ func (f *Fuzzer) AddCompilationTargets(compilations []compilationTypes.Compilati assertionTestMethods, propertyTestMethods, optimizationTestMethods := fuzzingutils.BinTestByType(&contract, f.config.Fuzzing.Testing.PropertyTesting.TestPrefixes, f.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes, - f.config.Fuzzing.Testing.TestViewMethods) + f.config.Fuzzing.Testing.TestViewMethods, + f.config.Fuzzing.Testing.PropertyTesting.TestReturnBool) contractDefinition.AssertionTestMethods = assertionTestMethods contractDefinition.PropertyTestMethods = propertyTestMethods contractDefinition.OptimizationTestMethods = optimizationTestMethods diff --git a/fuzzing/test_case_property_provider.go b/fuzzing/test_case_property_provider.go index 9ba141bb..5cabc930 100644 --- a/fuzzing/test_case_property_provider.go +++ b/fuzzing/test_case_property_provider.go @@ -102,6 +102,11 @@ func (t *PropertyTestCaseProvider) checkPropertyTestFailed(worker *FuzzerWorker, return true, executionTrace, nil } + // If the properties don't have a return value + if !worker.fuzzer.config.Fuzzing.Testing.PropertyTesting.TestReturnBool { + return false, executionTrace, nil + } + // Decode our ABI outputs retVals, err := propertyTestMethod.Method.Outputs.Unpack(executionResult.Return()) if err != nil { diff --git a/fuzzing/utils/fuzz_method_utils.go b/fuzzing/utils/fuzz_method_utils.go index 88404c48..08550ad1 100644 --- a/fuzzing/utils/fuzz_method_utils.go +++ b/fuzzing/utils/fuzz_method_utils.go @@ -24,12 +24,16 @@ func IsOptimizationTest(method abi.Method, prefixes []string) bool { // IsPropertyTest checks whether the method is a property test given potential naming prefixes it must conform to // and its underlying input/output arguments. -func IsPropertyTest(method abi.Method, prefixes []string) bool { +func IsPropertyTest(method abi.Method, prefixes []string, propertyTestReturnBool bool) bool { // Loop through all enabled prefixes to find a match for _, prefix := range prefixes { // The property test must simply have the right prefix and take no inputs and return a boolean if strings.HasPrefix(method.Name, prefix) { - if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.BoolTy { + // If returnBool is set to true, the property return a boolean, otherwise it return nothing + if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.BoolTy && propertyTestReturnBool { + return true + } + if len(method.Inputs) == 0 && len(method.Outputs) == 0 && !propertyTestReturnBool { return true } } @@ -38,9 +42,9 @@ func IsPropertyTest(method abi.Method, prefixes []string) bool { } // BinTestByType sorts a contract's methods by whether they are assertion, property, or optimization tests. -func BinTestByType(contract *compilationTypes.CompiledContract, propertyTestPrefixes, optimizationTestPrefixes []string, testViewMethods bool) (assertionTests, propertyTests, optimizationTests []abi.Method) { +func BinTestByType(contract *compilationTypes.CompiledContract, propertyTestPrefixes, optimizationTestPrefixes []string, testViewMethods bool, propertyTestReturnBool bool) (assertionTests, propertyTests, optimizationTests []abi.Method) { for _, method := range contract.Abi.Methods { - if IsPropertyTest(method, propertyTestPrefixes) { + if IsPropertyTest(method, propertyTestPrefixes, propertyTestReturnBool) { propertyTests = append(propertyTests, method) } else if IsOptimizationTest(method, optimizationTestPrefixes) { optimizationTests = append(optimizationTests, method)